只要你的项目可以运行 CKEditor 4,(CKEditor 4的下载使用再上一篇有提到)复制粘贴下面的代码,删除你不需要的事件和父组件传过来的值,还有图片,你可以替换成其他的图片,基本上可以运行查看效果。 实现步骤在下面。
实例:
<template>
<div class='indexBox'>
<!-- 考试管理,练习管理--考题管理,批量新增 -->
<el-button type="primary" size="small" v-if="programId" @click='batchAddprogramid'>保存2</el-button>
<!-- 试题管理-批量新增 -->
<el-button type="primary" size="small" v-else @click='batchAdd'>保存1</el-button>
<el-button type="info" size="small" @click="explain">说明</el-button>
<el-button type="warning" size="small" @click="running">运行</el-button>
<div class='index'>
<div class="questionWrite">
<ckeditor id="editor1" @ready="onEditorReady"></ckeditor>
</div>
<div class="questions">
<div class="questionModel" v-for="(item, index) in questions" :key="item.id">
<span>{{ index + 1 }}.<font style="color:#0095FF">【{{ item.qTypeText }}】</font></span>
<template v-if="item.qType == 1">
<p class="title" v-html="item.title"></p>
<el-radio-group class="el-radio-group" v-model="item.qAnswer">
<el-radio disabled :label="index" v-for="(item, index) in item.options" :key="index + '1'">
<span v-html="item"></span>
</el-radio>
</el-radio-group>
</template>
<template v-if="item.qType == 2">
<p class="title" v-html="item.title"></p>
<el-checkbox-group class="duoxuan" v-model="item.qAnswer">
<el-checkbox disabled :label="index" v-for="(item, index) in item.options"
:key="index + '2'"><span v-html="item"></span></el-checkbox>
</el-checkbox-group>
</template>
<template v-if="item.qType == 3">
<p class="title" v-html="item.title"></p>
<el-radio-group class="el-radio-group" v-model="item.qAnswer">
<el-radio disabled label="对" value="对"></el-radio>
<el-radio disabled label="错" value="错"></el-radio>
</el-radio-group>
</template>
<template v-if="item.qType == 4">
<p style="display: inline;" class="title" v-html="item.title"></p>
<div class="answer">参考答案:<span v-html="item.qAnswer.join('')"></span></div>
</template>
<template v-if="item.qType == 5">
<p class="title" v-html="item.title"></p>
<el-input type="textarea" autosize placeholder="" disabled></el-input>
<div class="answer">参考答案:<span v-html="item.qAnswer.join('')"></span></div>
</template>
<div class="analysis">
解答参考:<span v-html="item.qAnalysis"></span>
</div>
</div>
</div>
</div>
<el-dialog title="说明" :visible.sync="explainDialog" append-to-body width="600px" align="left"
:close-on-click-modal="false">
<div class="boxText">
说明:<br>
1、题目与题目之间需空一行,题目可以不加题号,题干中间不得换行<br>
2、题干与选项,及各选项之间需回车换行<br>
3、解答参考永远再最后一个,不能换行<br>
4、单选题,多选题,判断题填写答案必须以 "正确答案" 关键词开头<br>
5、填空题,问答题,填写答案必须以 "参考答案" 关键词开头,并换行写答案<br>
6、填空题题干间以{}表示填空内容,参考答案一行一个<br>
7、编辑之后,点击运行按钮可在右侧查看效果,<br>
</div>
<el-image v-if="isShowimg == true" style="width: 100px; height: 100px" src="../../../static/imgModel.jpg"
:preview-src-list="['../../../static/imgModel.jpg']"></el-image>
<el-button type="primary" size="small" v-if="isShowimg == false" @click="lookImg">试题模板</el-button>
<span slot="footer" class="dialog-footer">
<el-button @click="explainDialog = false">关 闭</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import CKEditor from 'ckeditor4-vue';
export default {
name: "ckeditor1",
components: {
ckeditor: CKEditor.component
},
props:['programId'],
data() {
return {
isShowimg: false,//试题模板图片显示
editorDom: null,
headers: {
'Authorization': localStorage.getItem('token')
},
checkList: [],
radio: "",//单选选择项
textarea1: "",
textarea: `
<p>6. 图示容器内盛有密度分别为<img alt="" height="29" src="http://192.168.0.123:9995/2024/6/25/1805495399415287808.png" width="65" />的两种液体,。</p>
<p>参考答案:</p>
<p><img alt="" height="46" src="http://192.168.0.123:9995/2024/6/25/1805495399415287810.png" width="371" /></p>
<p> </p>
<p></p>
<p>37.双头螺柱旋入较厚零件螺孔的一端称为{},与螺母旋合的一端称为{}。</p>
<p>参考答案:( 旋入端 ) ( 紧固端 )</p>
<p> </p>
<p>21.简述路基工程的特点和路基工程的基本组成。</p>
<p>参考答案:</p>
<p>特点:(1)构筑材料: 路基主要由松散的土、石等散粒体介质,性质复杂,状态容易变化。</p>
<p>(2)构筑环境:路基通常直接建造在地层上,在铁路的修建过程中往往要面对复杂的地基条件和艰险的地形条件。</p>
<p>(3)运营环境:路基完全暴露在大自然中,时刻受到自然条件变化的侵袭和破坏;此外,路基要同时承受轨道静荷载和列车动荷载的作用。</p>
<p>基本组成:路基本体(包含地基), 路基排水设备, 路基防护和加固建筑物, 路基接口</p>
<p>解答参考:没答应</p>
<p> </p>
<p>21. 简述路基工程的特点和路基工程的基本组成。</p>
<p>参考答案:</p>
<p>特点:(1)构筑材料: 路基主要由松散的土、石等散粒体介质,性质复杂,状态容易变化。</p>
<p style="margin-left:75px">(2)构筑环境:路基通常直接建造在地层上,在铁路的修建过程中往往要面对复杂的地基条件和艰险的地形条件。</p>
<p style="margin-left:75px">(3)运营环境:路基完全暴露在大自然中,时刻受到自然条件变化的侵袭和破坏;此外,路基要同时承受轨道静荷载和列车动荷载的作用。</p>
<p>基本组成:路基本体(包含地基), 路基排水设备, 路基防护和加固建筑物, 路基接口。</p>
<p> </p>
<p>6. 图示容器内盛有密度分别为<img alt="" height="29" src="http://192.168.0.123:9995/2024/6/25/1805495399415287808.png" width="65" />的两种液体,则有( )。</p>
<p><img alt="" height="141" src="http://192.168.0.123:9995/2024/6/25/1805495399415287811.png" width="217" /></p>
<p> (A) <img alt="" height="54" src="http://192.168.0.123:9995/2024/6/25/1805495399415287812.png" width="381" /></p>
<p> (B) <img alt="" height="54" src="http://192.168.0.123:9995/2024/6/25/1805495399415287809.png" width="370" /></p>
<p> (C) <img alt="" height="59" src="http://192.168.0.123:9995/2024/6/25/1805495399461425152.png" width="372" /></p>
<p> (D) <img alt="" height="46" src="http://192.168.0.123:9995/2024/6/25/1805495399415287810.png" width="371" /></p>
<p>你选择的答案: 未选择 [错误]</p>
<p>正确答案:AB</p>
<p>解答参考: </p>
<p> </p>
<p>6. 图示容器内盛有密度分别为<img alt="" height="29" src="http://192.168.0.123:9995/2024/6/25/1805495399415287808.png" width="65" />的两种液体,则有( )。</p>
<p><img alt="" height="141" src="http://192.168.0.123:9995/2024/6/25/1805495399415287811.png" width="217" /></p>
<p> (A) <img alt="" height="54" src="http://192.168.0.123:9995/2024/6/25/1805495399415287812.png" width="381" /></p>
<p> (B) <img alt="" height="54" src="http://192.168.0.123:9995/2024/6/25/1805495399415287809.png" width="370" /></p>
<p> (C) <img alt="" height="59" src="http://192.168.0.123:9995/2024/6/25/1805495399461425152.png" width="372" /></p>
<p> (D) <img alt="" height="46" src="http://192.168.0.123:9995/2024/6/25/1805495399415287810.png" width="371" /></p>
<p>你选择的答案: 未选择 [错误]</p>
<p>正确答案:A</p>
<p>解答参考: </p>
<p> </p>
<p>3. 已知某变径有压管段的管径之比,则相应的雷诺数之比( )。</p>
<p> (A) 1</p>
<p> (B) 2</p>
<p> (C) 3</p>
<p> (D) 4</p>
<p>你选择的答案: 未选择 [错误]</p>
<p>正确答案:B D</p>
<p>解答参考:</p>
<p> </p>
<p>1. 按状态,可将流体分为( )</p>
<p> (A) 对</p>
<p> (B) 错</p>
<p>解答参考: </p>
<p> </p>
<p>2. 单位质量力是指作用在单位( )流体上的质量力。</p>
<p> (A) 面积 </p>
<p> (B) 体积</p>
<p> (C) 质量</p>
<p> (D) 重量</p>
<p>你选择的答案: 未选择 [错误]</p>
<p>正确答案:选择的答案选择的答案选择的答案选择的答案选择的答案</p>
<p>解答参考: </p>
`,
questionIndex: 0,
isHaveanalysis: false,
isHaveAnswer5: false,
isHaveAnswer123: false,
questions: [],
explainDialog: false,
isBlur: false,
};
},
watch: {},
methods: {
lookImg() {
this.isShowimg = true
},
explain() {
this.explainDialog = true
},
running() {
if (this.editorDom) {
if (this.editorDom.getData()) {
this.textareaC(this.editorDom.getData())
}
}
},
onEditorReady(event) {
if (this.editorDom) {
// this.editorDom.setData();
this.editorDom.setData(this.textarea);
}
},
// g:全局匹配
// m:多行匹配
// /\s+/:多个连续空格或一个空格
// /\n+/:一个enter或多个
// /\n\s*\n/gm:只以中间空了一行 或 空了几行 做分隔符
textareaC(val) {
this.questions = []
let text = val
// .replace(/<p><\/p>\s*/mg,'')
// const regex2 = /<p> <\/p>/gm;
const regex2 =/<p>(?:\s| )*<\/p>/gm;
let qutetionItem = text.split(regex2)
let answerRegb = /\{.*?\}/g
const regex = /<p\b[^>]*>(.*?)<\/p>/gm;
let regeAnswer = /^正确答案{1}/;
let regeAnswer5 = /^参考答案{1}/;
let regeAnalysis = /^解答参考{1}/;
// const regex = /\((.*?)\)/; // 使用非贪婪匹配 括号是英文状态下的 regex.exec(val)[1]括号里面的内容
// const regex = /\{.*?\}/g; // 使用非贪婪匹配 花括号是中英文状态下的 regex.exec(val)[0]括号里面的内容
// let ste="这是填空这是填空这是填空这是填空这是填空这是填空这是填空[这是]填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空这是填空题?[填空题]"
console.log(qutetionItem);
// qutetionItem:带标签的整个试题
qutetionItem.forEach(item => {
console.log("99999");
console.log(item);
let title = '';//题目
let typeText = '单选题';//类型文字
let typeNum = 10;//类型数字标识
let answer = [];//答案
let analysis = '';//解析
let options = [];//选项
let Itemrows = (item.split(/\n+/))
console.log(Itemrows); //['这是单选题[单选题]', '1', '4', '3','答案:1,4,2','解析:ccc']
let rows = []
Itemrows.forEach((item1, index) => {
if (item1 != '') {
rows.push(item1)
}
})
title = rows[0]
// console.log(rows);
let match
// /<p\b[^>]*>(.*?)<\/p>/gm; 以p标签分割每一行
while ((match = regex.exec(item)) !== null) {
console.log(item);
console.log("hjkhjk");
// console.log(match[1]); // 这将打印出每个<p>标签内的内容,不包括<p>标签本身
// 如果有'正确答案',以答案判断是单选还是多选还是判断
if (match[1].match(regeAnswer) != null) {
this.isHaveAnswer123 = true
// console.log(match[1].slice(5));//A 对 AB 三角形
let val = match[1].slice(5)
if (val) {
if (val == '对' || val == '错') {
typeNum = 3
typeText = '判断题'
answer = val
} else {
let arr = val.split('');//['A', 'B', 'D']
answer = this.formatAnswer(arr);//[1, 2, 4]
if (answer.length == 1) {
typeNum = 1
typeText = '单选题'
answer = Number(answer.join(','))
// answer =answer.toString().split('')
} else if (answer.length > 1) {
typeNum = 2
typeText = '多选题'
}
}
}
}
// 如果有'参考答案',以"参考答案"判断是问答题,填空题,图片题
if (match[1].match(regeAnswer5) != null) {
let isHavekuohao = title.match(answerRegb)
if (isHavekuohao != null) { //题目有{},说明是填空题
typeNum = 4
typeText = '填空题'
} else {
typeNum = 5
typeText = '问答题'
}
}
// 如果有'解答参考'
if (match[1].match(regeAnalysis) != null) {
this.isHaveanalysis = true
let dd = match[1].slice(5)
analysis = `</p>${dd}</p>`
}
}
if (typeNum == 4 || typeNum == 5) {
let [titleRow, ...answerArr] = rows
let answerArrTrue = []
options = []
if (this.isHaveanalysis == true) {
answerArrTrue = answerArr.slice(1, answerArr.length - 1)
let dd = answerArr[answerArr.length - 1]
analysis = `</p>${dd.slice(8)}</p>`
} else {
answerArrTrue = answerArr.slice(1)
analysis = ''
}
this.questions.push(this.formatQuestion(title, typeNum, typeText, options, answerArrTrue, analysis))
this.isHaveanalysis = false
}
if (typeNum == 1 || typeNum == 2 || typeNum == 3) {
let [titleRow, ...optionsarr] = rows
if (this.isHaveanalysis == true && this.isHaveAnswer123 == true) {
options = optionsarr.slice(0, optionsarr.length - 2)
} else if (this.isHaveanalysis == true || this.isHaveAnswer123 == true) {
options = optionsarr.slice(0, optionsarr.length - 1)
} else {
options = optionsarr
}
this.isHaveanalysis = false
this.isHaveAnswer123 = false
this.questions.push(this.formatQuestion(title, typeNum, typeText, options, answer, analysis))
}
});
console.log(this.questions);
},
// 格式化题
formatQuestion(title, questionType, typeText, options, answer, analysis) {
let question = {
id: this.questionIndex++,
title: title,
qType: questionType,
qTypeText: typeText,
qAnswer: answer,
qAnalysis: analysis,
};
console.log();
if (options.length > 0) {
let tmpOptions = [];
options.forEach((item, index) => {
tmpOptions.push(item);
});
question.options = tmpOptions;
}
return question;
},
submitBefore(){
let arr = []
let questionCopy = JSON.parse(JSON.stringify(this.questions))
questionCopy.forEach(item => {
if (item.qType == 1 || item.qType == 3) {
item.qAnswer = item.qAnswer.toString().split('')
}
if (item.qType == 2) {
item.qAnswer = item.qAnswer.map(String);
}
if (item.qType == 5) {
item.qAnswer = [item.qAnswer.join(' ')];
}
let data = {
answer: item.qAnswer,
answerAnalysis: item.qAnalysis,
nameHtml: item.title,
options: item.options,
type: item.qType,
}
// if(item.title){
arr.push(data)
// }
})
return arr
},
// 试题管理--批量新增保存
batchAdd() {
let dataArr=this.submitBefore()
if (dataArr.length > 0) {
this.postAxios(this.$api.API.examination.examinationquestionbatchAdd, dataArr).then(resp => {
if (resp.code == 200) {
this.$message.success('新增成功');
this.$emit('successEvent', false)
} else {
this.$message.error(resp.message);
}
})
}
},
// 考试管理,练习管理--考题管理--批量新增试题
batchAddprogramid(){
let listArr=this.submitBefore()
if (listArr.length > 0) {
this.postAxios(this.$api.API.examinationProgram.questionInsertbatch, {
list:listArr,
programId:this.programId,
}).then(resp => {
if (resp.code == 200) {
this.$message.success('新增成功');
this.$emit('successEvent', false)
} else {
this.$message.error(resp.message);
}
})
}
},
// 判断题目当中是否 有写题型
isHaveType(text) {
let have
if (text == '[单选题]') {
have = true
} else if (text == '[多选题]') {
have = true
} else if (text == '[判断题]') {
have = true
} else if (text == '[填空题]') {
have = true
} else if (text == '[问答题]') {
have = true
} else if (text == '[图片题]') {
have = true
} else {
have = false
}
return have
},
// 格式化答案 答案转换成1234
formatAnswer(arr) {
let newArr = []
arr.forEach(item => {
if (item == 'A') {
item = 0
} else if (item == 'B') {
item = 1
} else if (item == 'C') {
item = 2
} else if (item == 'D') {
item = 3
} else if (item == 'E') {
item = 4
} else if (item == 'F') {
item = 5
} else if (item == 'G') {
item = 6
} else if (item == 'H') {
item = 7
} else if (item == 'I') {
item = 8
} else if (item == 'J') {
item = 9
} else if (item == 'K') {
item = 10
} else if (item == 'L') {
item = 11
} else if (item == 'M') {
item = 12
} else if (item == 'N') {
item = 13
} else {
item = 0
}
newArr.push(item)
})
return newArr
}
},
mounted() {
console.log(this.programId);
this.editorDom = CKEDITOR.replace('editor1', {
width: '100%',
height: '736px'
});
if (this.editorDom) {
this.editorDom.on('blur', () => {
// this.textareaC(this.editorDom.getData())
})
}
}
}
</script>
<style scoped>
.index {
width: 100%;
margin: 8px auto 0px;
height: 800px;
display: flex;
justify-content: space-between;
}
.questionWrite {
width: 49%;
}
p {
display: inline-block !important;
}
.textarea>>>.el-textarea__inner {
width: 100%;
height: 800px;
/* //禁止textarea可以手动拉伸 */
resize: none;
overflow: auto;
padding: 20px 10px 10px 10px;
line-height: 20px;
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "阿里巴巴普惠体", "思源黑体 CN", "微软雅黑", "Microsoft YaHei", sans-serif;
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
font-style: inherit;
font-weight: inherit;
&::-webkit-scrollbar {
width: 0;
}
;
}
.questions {
width: 50%;
border: 1px solid #c2c2c2;
box-sizing: border-box;
overflow-y: auto;
padding: 10px 10px 10px 10px;
}
.questionModel {
margin-bottom: 30px;
padding: 8px 8px 15px 8px;
}
.questionModel:hover {
background-color: #efefef;
box-sizing: border-box;
}
.title {
font-size: 15px;
margin-bottom: 10px;
display: inline;
}
/* 单选框 */
.el-radio-group {
width: 100%;
}
.el-radio {
width: 100%;
margin-bottom: 8px;
display: flex;
align-items: center !important;
white-space: pre-wrap !important;
}
.el-radio-group>>>.el-radio__inner {
width: 20px;
height: 20px;
cursor: pointer !important;
}
.el-radio-group>>>.el-radio__label {
font-size: 15px;
color: #333 !important;
cursor: pointer !important;
}
.el-radio-group>>>.is-disabled .el-radio__inner {
cursor: pointer !important;
background-color: #ffffff !important;
border-color: #a1a1a1 !important;
}
.el-radio-group>>>.is-checked .el-radio__inner {
background-color: #409EFF !important;
border: 0px solid #ffffff !important;
}
.el-radio-group>>>.is-disabled .el-radio__inner::after {
cursor: pointer !important;
}
.el-radio-group>>>.el-radio__input.is-disabled.is-checked .el-radio__inner::after {
background-color: #ffffff;
}
.answer {
font-size: 15px;
}
.analysis {
font-size: 15px;
color: #0095FF;
}
/* 多选框 */
.el-checkbox {
width: 100%;
margin-bottom: 8px;
display: flex;
align-items: center !important;
white-space: pre-wrap !important;
}
.duoxuan>>>.el-checkbox__inner {
width: 18px;
height: 18px;
background-color: #ffffff !important;
cursor: pointer !important;
border: 1px solid #dfdbdb !important;
}
.duoxuan>>>.el-checkbox__label {
font-size: 15px;
color: #333 !important;
cursor: pointer !important;
}
.duoxuan>>>.el-checkbox__inner::after {
width: 6px;
height: 10px;
left: 5px;
border-color: #ffffff !important;
cursor: pointer !important;
}
.duoxuan>>>.is-checked .el-checkbox__inner {
background-color: #409EFF !important;
color: #ffffff !important;
}
.boxText {
line-height: 22px;
margin-bottom: 10px;
}
</style>
实现步骤:
第一步:以空一行分割成每一道完整题(包括题干,选项,答案,解析)在富文本里面空的一行表现为<p> </p>, 如下图:表示有10到题(先忽略数组第二项)。
第二步:再循环上面图片的数据,每一道完整的题以/n分割成包括p标签的数据,如下图:表示一个完整的题是一个数组。
第三步:要去除“ ”的内容,所以上面图片循环重新调整一下:
//rows就表示以上图片除去了" " 的数组,第一项就是这道题的题干
let rows = []
Itemrows.forEach((item1, index) => {
if (item1 != ' ') {
rows.push(item1)
}
})
第四步: 以正则匹配答案,来判断是什么类型的试题。只要知道是什么类型的试题就可以传参利用函数,组成一到完整的题。