这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战
考试系统实战第二篇,今天继续介绍考试系统-学生端-学生答题页面 在前面的文章 Vue-考试系统实战(一)介绍了单选,判断和多选题,这篇文章介绍一下连线,排序和问答题。其中用到了indexedDB前端数据库,防止断电断网时候数据丢失,前端存储,下次学生进入检测到有数据时候进行数据恢复。
其中连线题用到了canvas绘制,连线题是多对多。可参考# 使用html5+canvas+Jquery实现的纯代码连线题 排序题用到了jquery-ui的sortable和resizable。之前用过觉得好用,于是选择了这个插件 问答题用到了富文本编辑器。tinymce,功能还是比较强大的,可以插入公式,表情等。
下面我们一起看看吧~
一:html
问答题的编辑器tinymce
<div class="tinymce-box" style="flex:1">
<editor v-model="myValue"
ref="formulaContentEditor"
:class="[disabled?'disabled':'']"
:init="init"
:disabled="disabled"
@onClick="onClick">
</editor>
<p class="word"><span>{{txtLength}}字</span><i v-if="maxlength>=0">/{{maxlength}}字</i></p>
</div>
3种题型的html
<!--连线题-->
<template v-if="item1.qtype === 5">
<div class="completion">
<div class="matchingSubjectBox">
<div class="demo2 option" style="padding: 0;">
<div class="show cb">
<div :class="['tools']">
<el-button class="goBackBtn" type="primary">回退</el-button>
<el-button class="resetCanvasBtn" type="primary">重置</el-button>
</div>
<div class="showleft">
<template v-for="items in item.options">
<template v-if="items.isLeft===0">
<span :class="['showitem',items.overflow?'txt-overflow':'']" :title="items.opName" :data-id="items.id" :ques-id="items.questionId" :key="items.id">{{items.opName}}</span>
</template>
</template>
</div>
<div class="showright">
<template v-for="items in item.options">
<template v-if="items.isLeft===1">
<span :class="['showitem',items.overflow?'txt-overflow':'']" :title="items.opName" :data-id="items.id" :ques-id="items.questionId" :key="items.id">{{items.opName}}</span>
</template>
</template>
</div>
<canvas class="canvas"></canvas>
<canvas class="backcanvas"></canvas>
</div>
</div>
</div>
</div>
</template>
<!--排序题-->
<template v-if="item1.qtype === 6">
<div class="completion">
<div class="sortOption">
<p :label="item2.opNumber" :key="item2.opNumber" v-for="item2 in item.options" v-html="item2.char+' '+item2.opName"></p>
</div>
</div>
<div class="sortBottom">
<p>排序 <i>(可拖动选项移动顺序)</i> </p>
<div class="sortDiv">
<ul class="sortUl" :id="index">
<li v-for="item1 in item.sortList"
:id="item1.id"
:key="item1.value"
:value="item1.value"
:class="['sortItem',item.isAnswerd === 1?'active':'']" @click="draggableClick(item, index,2,indexs)">
{{item1.label}}
</li>
</ul>
</div>
</div>
</template>
<!--问答题-->
<template v-if="item1.qtype === 7">
<div class="completion">
<ckeditor4 :size="{height:height}"
ref="editor+'index'"
v-model="item.answerd"
:disabled="disabled"
:maxlength="20000"
@input="inputTinymce8(itemP,item.indexNew,item,index,item.id)"
>
</ckeditor4>
</div>
</template>
二:js
import tinymce from '@/components/common/Tinymce.vue';// 编辑器
import $ from "jquery";// jquery
import "jquery-ui/ui/widgets/sortable";
import "jquery-ui/ui/widgets/resizable";
methods: {
// 连线
contact(type = 5) {
this.$nextTick(() => {
$(".demo").each((index, item) => {
let dom = $(item).find(".show");
let width = dom.innerWidth();
let height = dom.innerHeight();
onLine({
type: type,
regainCanvas: true,
obj: $(item),
width: width,
height: height,
changeLine: this.changeLine
})
})
})
},
},
// 连线大框
contactAns(ques) {
this.$nextTick(()=>{
let objList;
objList = [];
$(".demo").each((index,item)=>{
let dom = $(item).find(".show");
let width = dom.innerWidth();
let height = dom.innerHeight() || $(item).parents(".subject").find(".matchingSubjectBox").height();
let part1 = $(item).find(".showleft");
let part2 = $(item).find(".showright");
// 初始化赋值 列表内容
$(item).find(".showleft").children("span").each(function() {
$(this).attr({group:"gpl",left:$(this).position().left+$(this).outerWidth(),top:$(this).position().top+$(this).outerHeight()/2,sel:"0",check:"0"});
});
$(item).find(".showright").children("span").each(function() {
$(this).attr({group:"gpr",left:$(this).position().left,top:$(this).position().top+$(this).outerHeight()/2,sel:"0",check:"0"});
});
part1.attr('first',0);//初始赋值 列表内容容器
part2.attr('first',0);
let canvas = $(item).find(".canvas")[0]; //获取canvas实际连线标签
canvas.width = width; //canvas宽度等于div容器宽度
canvas.height = height;
let leftPair = ques[index].stuAnsLeftIds;
let rightPair = ques[index].stuAnsRightIds;
this.newCanvas(canvas,leftPair,rightPair,part1,part2);
})
})
},
/**
* 绘制连线
* c:canvas
* leftPair: 左侧选项index: eg:[0,1,2]
* rightPair: 右侧选项对应链接的左侧选项的index: eg:[2, 0, 1]
* part1: 左侧连线框div
* part2: 右侧连线框div
* */
newCanvas(c,leftPair,rightPair,part1,part2) {
let lineStyleActive = "#5C8EF2";
let lineStyleCorrect = "#18C588";
let lineStyleError = "#FF644B";
let mx=[];// 连线坐标
let my=[];
let mx2 = [];
let my2 = [];
let pairA = new Array();
let size = leftPair.length;
let size2 = rightPair.length;
let sizeCont = size;// 左侧和右侧 连线数量
if(size2 > size) sizeCont = size2;
for(let i=0; i<sizeCont; i++){
if( typeof rightPair[i] == "number"){
pairA.push(rightPair[i]);
pairA.push(i);
if(size2>size){
part2.find(".showitem").eq(rightPair[i]).addClass("addstyle").attr("check", "1").attr("pair", rightPair[i]);
part1.find(".showitem").eq(leftPair[i]).addClass("addstyle").attr("check", "1").attr("pair", leftPair[i]);
}else{
part1.find(".showitem").eq(leftPair[i]).addClass("addstyle").attr("check", "1").attr("pair", leftPair[i]);
part2.find(".showitem").eq(rightPair[i]).addClass("addstyle").attr("check", "1").attr("pair", rightPair[i]);
}
}
}
let leftPoint;
let leftPointEnd,topPointEnd;
let topPoint;
for (let i = 0; i < leftPair.length; i++) {
leftPoint = parseInt(part1.find(".showitem").eq(leftPair[i]).attr("left"));
topPoint = parseInt(part1.find(".showitem").eq(leftPair[i]).attr("top"));
leftPointEnd = parseInt(part2.find(".showitem").eq(rightPair[i]).attr("left"));
topPointEnd = parseInt(part2.find(".showitem").eq(rightPair[i]).attr("top"));
mx[i] = leftPoint;
my[i] = topPoint;
mx2[i] = leftPointEnd;
my2[i] = topPointEnd;
}
let ctx = c.getContext("2d");
ctx.save();
ctx.beginPath();
for (let i=0;i<mx.length;i++) { // 遍历绘制
ctx.moveTo(mx[i], my[i]);// 起点
ctx.lineTo(mx2[i], my2[i]);// 终点
}
ctx.strokeStyle =lineStyleActive;
ctx.stroke();// 绘制
ctx.restore();
},
// 排序
draggableFun() {
let timer;
timer = setInterval(() => {
if($(".sortUl").length>0) {
$(".sortUl").sortable({
connectWith: ".sortUl",
placeholder: "sortItem",
update: (event, ui) => {
let quesId = $(ui.item).parents('.sortBottom').prevAll('.stem').attr('ques-id');
this.questionTypes.forEach((item) => {
$(ui.item).parents('.sortUl').find("li").addClass("active");
item.qidList.forEach((item2, index2) => {
if (item2.qid == quesId) {
item.radioList[index2].flag = 4;
}
if (commonJs.isNullVal(item2.qids) && item2.qids.length>0) {
item2.qids.forEach((item3, index3) => {
if (item3 == quesId) {
item.radioList[index2].radioList1[index3].flag = 4;
}
})
}
})
})
},
receive: (event, ui) => {
if (ui.sender.attr('id') != $(ui.item).parents('.sortUl').attr('id')) {
$(".sortUl").sortable("cancel");
}
}
});
clearInterval(timer);
}
}, 1000);
},
// 排序点击 进度+1
draggableClick(item,index,flag,indexTwo) {
if(flag === 1){// 排序题
$(".collapse6 .subject6").each(function(i6,s6){
if(i6 === index){
$(s6).find(".sortItem").addClass("active");
}
})
}else if(flag===2){// 阅读理解下的排序题
$(".collapse8 .subject8").each(function(i8,s8){
if(i8===index){
$(s8).find(".subject6").each(function(i6,s6){
if(i6 === indexTwo){
$(s6).find(".sortItem").addClass("active");
}
})
}
})
}
let quesId = item.id;
this.questionTypes.forEach((item) => {
item.qidList.forEach((item2, index2) => {
if (item2.qid == quesId) {
item.radioList[index2].flag = 4;
}
if (commonJs.isNullVal(item2.qids) && item2.qids.length>0) {
item2.qids.forEach((item3, index3) => {
if (item3 == quesId) {
item.radioList[index2].radioList1[index3].flag = 4;
}
})
}
})
})
},
希望怼你有帮助。做项目时候是vue小白,代码比较冗余。总结记录一下。