Vue-考试系统实战(二)

·  阅读 36
Vue-考试系统实战(二)

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

考试系统实战第二篇,今天继续介绍考试系统-学生端-学生答题页面 在前面的文章 Vue-考试系统实战(一)介绍了单选,判断和多选题,这篇文章介绍一下连线,排序和问答题。其中用到了indexedDB前端数据库,防止断电断网时候数据丢失,前端存储,下次学生进入检测到有数据时候进行数据恢复。

其中连线题用到了canvas绘制,连线题是多对多。可参考# 使用html5+canvas+Jquery实现的纯代码连线题 排序题用到了jquery-ui的sortable和resizable。之前用过觉得好用,于是选择了这个插件 问答题用到了富文本编辑器。tinymce,功能还是比较强大的,可以插入公式,表情等。

QQ图片20211119113441.png

下面我们一起看看吧~

一:html

问答题的编辑器tinymce

QQ图片20211119113437.png

<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

QQ图片20211119113431.png

QQ图片20211119113434.png

<!--连线题-->
<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小白,代码比较冗余。总结记录一下。

分类:
前端
标签: