習慣了將解析寫在代碼注釋,這里就直接上代碼啦,里面用到的bxm-ui3組件庫是博主基于element-Plus做的,可以通過npm i bxm-ui3自行安裝使用
// 識別方法:
// dom 當前識別數據所在區域, questionType 當前點擊編輯選擇的題目類型(論述題、簡答題要用)
export const recognitionMethod = (inputText, dom, questionType) => {// 存一份let newInputText = inputText.trim()let data = {questionContent: '',questionType: '',questionAnalysis: '',answerList: []}// 解析答案let { result, newText } = recognitionResult(newInputText)data.questionAnalysis = result || ''// 單選多選題匹配const regx1 = /(?:^\d+、)?(.*?)\s*[\((]\s*([A-Za-z]*)\s*[\))]\s*([\s\S]+)/// 填空題匹配 若下劃線上無答案,則三個下劃線為一個空const regx2 = /(?:^\d+、)?(.*?)[\_]+\s*/g// const regx2 = /(?:^\d+、)?(.*?)(_{3})+\s*/g// 判斷題匹配 含有(√|×|對|錯|正確|錯誤)const regx3 = /(?:^\d+、)?(.*?)\(([√×對錯正確錯誤])\)\s*/let match = newText.match(regx1)let match2 = newText.match(regx2)let match3 = newText.match(regx3)// 填空題:去根據dom獲取出來有下劃線的部分即為答案let underLineList = getUnderlineList(dom, newText)if (match) { // 基本的單選多選let answer = match[2] || ''let optionsStr = match[3]// 沒有答案或者只有一個答案識別為單選,多個答案為多選if (answer.length === 1 || !answer.length) {data.questionType = '00'} else {data.questionType = '01'}// 單選/多選,有選項if (optionsStr) {let options = []let regexOption = /[A-Za-z][.、.]\s*(?:.*?)(\([^)]*\))?(?=[A-Za-z][.、.]|$)/gsulet matchOption = nullwhile((matchOption = regexOption.exec(optionsStr)) !== null) {options.push(matchOption[0].replace(/[A-Za-z][\.、.]\s*/, '') + (matchOption[1] ? matchOption[1] : ''));}if (!options.length) {// 選項let optionRegx1 = /[A-Za-z](\.|、)/options = optionsStr.split(optionRegx1).filter(option => { return !['', '.', '、', '.'].includes(option) })}if (options.length) {options.map((item, index) => {let obj = {answerContent: item,answerOrd: `${index + 1}`,answerRight: false,answerTitle: checkIndex(index)}// 單選if (data.questionType === '00') {obj.answerRight = (checkIndex(index) === answer || checkIndex(index).toLocaleLowerCase() === answer) ? '0' : false} else { // 多選let answers = answer.split('')obj.answerRight = (answers.includes(checkIndex(index)) || answers.includes(checkIndex(index).toLocaleLowerCase())) ? '0' : '1'}data.answerList.push(obj)})}}handleQuestionContent(match[1], newText, data)} else if (match3) { // 判斷題data.questionType = '03'data.questionContent = match3[1] + '()'let answer = match3[2]for(let i = 0; i < 2; i++) {let obj = {answerOrd: `${i + 1}`,answerRight: i === 0 ? ['對', '正確', '√'].includes(answer) ? i : false : ['錯', '錯誤', '×'].includes(answer) ? i : false,answerTitle: i === 0 ? '正確' : '錯誤'}data.answerList.push(obj)}} else if (underLineList.length || match2) { // 填空題data.questionType = '02'let { questionContent, answerList } = recognitionPack(newText, underLineList)data.questionContent = questionContentdata.answerList = answerList} else { // 簡答題/論述題 沒有匹配其余的直接處理為論述題或簡答題// 當前點擊編輯選擇的題目類型如果不是論述題或簡答題,就默認設置為簡答題data.questionType = ['04', '06'].includes(questionType) ? questionType : '04'let newStr = ''// 去掉數字、開頭if (/^\d+、/.test(newInputText)) {newStr = newInputText.replace(/^\d+、/, '')} else {newStr = newInputText}// 一共6種可以解讀為答案的內容let resultRegx = /(答:)|(答案:)|(解析:)|(分析:)|(解答:)|(回答:)]/g// 給了解析if (resultRegx.test(newInputText)) {// ['題干', '第一種', '第二種'.....'最后一個是根據前面某一種分割出來的答案']如果有解析就是正常的8個項let arr = newStr.split(resultRegx)if (arr.length >= 8) {data.questionContent = arr[0].trim()data.questionAnalysis = arr[7].trim()} else {data.questionContent = newInputTextdata.questionAnalysis = ''}} else {data.questionAnalysis = ''data.questionContent = newStr.trim()}}return data
}// 序號A~Z-----AA~AZ
export const checkIndex = (index) => {let imn = Math.floor((index + 1)/26)let remainder = (index + 1) % 26if(imn === 0 || (imn === 1 && remainder === 0)) {// A~Zreturn String.fromCharCode(65 + index)}else if((imn > 1 || (imn === 1 && remainder > 0)) && imn <= 26){// AA、AB...BA...CA~ZZreturn (String.fromCharCode(65 + (remainder ? (imn - 1) : (imn - 2))) + String.fromCharCode(65 + (remainder ? (remainder - 1) : 25)))}
}// 解析答案
export const recognitionResult = (inputText) => {let result = ''let newText = inputText// 一共6種可以解讀為答案的內容let resultRegx = /(答:)|(答案:)|(解析:)|(分析:)|(解答:)|(回答:)]/g// 給了解析if (resultRegx.test(inputText)) {// ['題干', '第一種', '第二種'.....'最后一個是根據前面某一種分割出來的答案']如果有解析就是正常的8個項let arr = inputText.split(resultRegx)newText = arr[0].trim()if (arr.length >= 8) {result = arr[7]} else {result = ''}}return { result, newText }
}// 以下為填空題識別相關方法// 填空題識別
export const recognitionPack = (inputText, underLineList) => {let questionContent = ''let answerList = []let newStr = /^\d+、/.test(inputText) ? inputText.replace(/^\d+、/, '') : inputText// 這是下劃線上有內容if (underLineList.length) {underLineList.map((item, index) => {let obj = {answerOrd: index + 1,answerMoreSelect: item.answerMoreSelect,answerTitle: `第${index + 1}空答案`,inputVisible: false,inputValue: '',}answerList.push(obj)// 將答案替換成'___'let end = item.underLineStart + item.answerLength// 這里加了三個_,underLineList中剩余的項的unserLineStart都要處理,否則會錯位newStr = newStr.substring(0, item.underLineStart) + '___' + newStr.substring(end)// 處理下一個的unserLineStartif (index < underLineList.length - 1) {handleCheckUnderStart(index, underLineList)}})questionContent = newStr} else { // 這是下劃線上沒有內容,至少三個連續的_才識別成填空題,避免部分單詞識別錯誤,例如COMMENT_NODE// 找到下劃線let underRegx = /(_{3})+/g// let underRegx = /[\_]+/glet understrArr = newStr.match(underRegx) || []for (let i = 0; i < understrArr.length; i++) {// 將_替換成'___'let start = newStr.indexOf(understrArr[i])let end = start + understrArr[i].lengthnewStr = newStr.substring(0, start) + '___' + newStr.substring(end)}questionContent = newStrlet index = 0while(index < understrArr.length) {answerList.push({answerOrd: `${index + 1}`,answerMoreSelect: [],answerTitle: `第${index + 1}空答案`,inputVisible: false,inputValue: ''})index++}}return {questionContent,answerList}
}// 判斷節點是否有下劃線樣式
function isLeafWithUnderline(node) {if (node.nodeType === Node.TEXT_NODE) {return false}let style = window.getComputedStyle(node)// textDecoration含有underline的一定有下劃線return style.textDecoration && style.textDecoration.includes('underline')
}// 遞歸獲取到最深層葉子節點,遇到有下劃線的節點直接視為葉子節點
function findDeepestNodes(node, deepestNodes = []) {// 注釋節點if (node.nodeType === Node.COMMENT_NODE) { return deepestNodes }// 如果當前節點是文本節點或者具有下劃線樣式,認為是葉子節點if (node.nodeType === Node.TEXT_NODE || isLeafWithUnderline(node)) {deepestNodes.push(node)return deepestNodes // 返回當前節點,不再深入遍歷其子節點}// 遍歷當前節點的所有子節點for (let child of node.childNodes) {findDeepestNodes(child, deepestNodes)}return deepestNodes
}// 獲取下劃線列表
export const getUnderlineList = (dom, newText) => {let allTextNodes = findDeepestNodes(dom)let list = []let fullText = ''// 找到下劃線標簽進行數據處理for(let index = 0; index < allTextNodes.length; index++) {let node = allTextNodes[index]// 文本節點獲取內容和樣式是不一樣的let style = node.nodeType === Node.TEXT_NODE ? {} : window.getComputedStyle(node)fullText += !node?.innerText ? node.textContent : node.innerText// 去掉數字開頭fullText = /^\d+、/.test(fullText) ? fullText.replace(/^\d+、/, '') : fullText// 有下劃線的把下劃線內容記錄下來,下劃線位置記錄下來if (style?.textDecoration && style?.textDecoration.includes('underline') && node.innerText !== '') {let obj = {answerMoreSelect: node.innerText,answerTitle: `第${index + 1}空答案`,answerLength: node.innerText.length, // 答案長度underLineStart: fullText.length - node.innerText.length }list.push(obj)}}// 處理下劃線連在一起但是為u標簽時,要合并成一個空if (list.length) {for(let i = 0; i < list.length; i++) {// 連續的下劃線:if (i > 0 && list[i].underLineStart === list[i - 1].underLineStart + list[i - 1].answerLength) {list[i - 1] = {answerMoreSelect: list[i - 1].answerMoreSelect + list[i].answerMoreSelect, // 上一個的文本與當前文本組合answerTitle: `第${i}空答案`, // 只留前一個,所以下標是前一個的answerLength: list[i - 1].answerLength + list[i].answerLength, // 上一個的文本長度與當前文本長度之和underLineStart: list[i - 1].underLineStart // 上一個文本的起始位置就是最終的起始位置}list.splice(i, 1)i--}}}return list
}// 獲取增加或減少了多少長度
export const getChangeLen = (curUnderIndex, underList) => {let addLen = 0// 遍歷當前以及之前的for(let i = 0; i <= curUnderIndex; i++) {// 當前下劃線文本超出了下劃線3個字符的長度,替換成3個下劃線之后會少了 answerLength-3 的長度,后面的都需要往前移動answerLength-3個位置// 當前下劃線文本少于下劃線3個字符的長度,替換成3個下劃線之后會多了 3-answerLength 的長度,后面的都需要往后移動3-answerLength個位置if (underList[i].answerLength !== 3) {addLen += 3 - underList[i].answerLength // 變化的量可能正可能負}}return addLen
}// 處理下劃線起始位置
export const handleCheckUnderStart = (curUnderIndex, underList) => {if (curUnderIndex >= underList.length - 1) return// 獲取需要變動的數量let changeLen = getChangeLen(curUnderIndex, underList)// 處理當前的后一個即可underList[curUnderIndex + 1].underLineStart += changeLen
}// 處理選擇題的題干,獲取到答案并更新選項(題干中有多處為答案或者由多處括號,括號里是字母但不一定是答案的情況)
export const handleQuestionContent = (content, allText, data) => {if (!content || !allText) return ''let successContent = ''// 去掉數字開頭let newTextAll = allText.replace(/^\d+[.、.]\s*/, '')// 找到傳入的題干在所有字符串中的位置let contentIndex = newTextAll.indexOf(content)// 截取選項之前的內容比對let regx1 = /^(.*?)(?=\s*[A-Za-z]\.?[.、.])/slet regx2 = /^(.*?)(?=[A-Za-z](?:(?:\s*\.\s*)|(?:\s*,\s*)|$))/slet matchArr1 = newTextAll.match(regx1)let matchArr2 = newTextAll.match(regx2)let matchArr = []if (matchArr1 && matchArr2) { // 兩個都匹配比較誰匹配更接近matchArr = matchArr1[0].length > matchArr2[0].length ? matchArr1 : matchArr2} else if (matchArr1 || matchArr2) { // 有一個不能匹配直接獲取能匹配那個matchArr = matchArr1 ? matchArr1 : matchArr2} else {matchArr = null}// 已有的題干和真正的不同,需要對已有信息進行修改if (matchArr && matchArr.length > 0 && matchArr[0] !== content) {// 選項之前的內容successContent = matchArr[0]// 去掉空行successContent = successContent.replace(/(\r?\n\s*)+/g, '\n')let answers = data.answerList.map(item => { return item.answerTitle })// 從括號中找到真正的答案let answerKeyRegex = /[\((]\s*([A-Z]+)\s*[\))]/glet contentArr = successContent.split(answerKeyRegex)let resultContent = ''let successAnswerArr = []contentArr.map(item => {let regxAnswer = /^[A-Za-z]+$/g// 僅為大小寫字母if (regxAnswer.test(item)) {// 只有一個字母,并且字母在已生成的選項中,說明是其中的一個答案if (item.length === 1 && answers.includes(item.toLocaleUpperCase())) { // 替換成括號resultContent += '()'// 記錄出真正的答案,在最后去編輯選項設置選中!successAnswerArr.includes(item.toLocaleUpperCase()) && successAnswerArr.push(item.toLocaleUpperCase())} else if (item.length > 1) { /*** 多個字母需要判斷:* 1.字母有重復說明不是答案,直接還原* 2.字母不重復但是有字母不在已生成的選項中,直接還原* 3.字母不重復并且都在選項中為答案,同時將data中的試題類型修改為多選,選項默認選中項需要更改*/let itemArr = item.split('').filter(val => { return val !== '' })let newArr = [...new Set(JSON.parse(JSON.stringify(itemArr)))]if (itemArr.length !== newArr.length) { // 條件1resultContent += `(${item})`} else {let isInner = truefor(let i = 0; i < newArr.length; i++) {newArr[i] = newArr[i].toLocaleUpperCase()if (!answers.includes(newArr[i])) { // 條件2resultContent += `(${item})`isInner = falsebreak // 退出循環}}// 條件3,記錄正確選項if (isInner) {// 替換成括號resultContent += '()'// 記錄不重復的答案successAnswerArr = [...new Set(successAnswerArr.concat(itemArr))]}}} else {// 還原resultContent += `(${item})`}} else {resultContent += item}})// 更新題干data.questionContent = resultContent// 更新試題類型if (successAnswerArr.length > 1) {data.questionType = '01'} else {data.questionType = '00'}// 處理選項data.answerList.map((item, index) => {// 當前項為答案要默認選中if (successAnswerArr.includes(item.answerTitle)) {item.answerRight = data.questionType === '00' ? index : '0'} else {item.answerRight = data.questionType === '00' ? false : '1'}})} else {data.questionContent = content + '()'}
}
這是我簡單自定義的一個編輯器,其實是一個contenteditable的div,對里面內容進行簡單處理了之后就可以使用了
<template><div class="custom-editor":style="{height: height + 'px'}"><div class="custom-editor-placeholder" :style="{ display: content ? 'none' : 'block' }">{{ placeholder }}</div><div class="custom-editor-content" id="cusEditor":contenteditable="!disabled"></div></div>
</template><script setup>
import { onMounted, ref } from 'vue'const props = defineProps({height: {type: Number,default: 300},disabled: {type: Boolean,default: false},placeholder: {type: String,default: ''}
})let content = ref('')
let customEditor = ref(null)const emits = defineEmits(['change'])onMounted(() => {customEditor.value = document.getElementById('cusEditor')customEditor.value.addEventListener('input', (e) => {content.value = e.target.innerTextemits('change', customEditor.value.innerText)})// 自定義粘貼,去掉圖片,更改文字顏色(匹配系統顏色)customEditor.value.addEventListener('paste', async (e) => {e.preventDefault()let htmlContent = ''// 嘗試從現代API獲取HTML內容if (e.clipboardData && e.clipboardData.types.includes('text/html')) {htmlContent = e.clipboardData.getData('text/html')} else if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.getData) {htmlContent = e.originalEvent.clipboardData.getData('text/html')} else {htmlContent = (e.clipboardData || window.clipboardData).getData('text')}// 獲取粘貼的純文本,便于后面比較,避免粘貼內容不全let pasteText = (e?.clipboardData || window?.clipboardData)?.getData('text')// 保存當前的選區const selection = window.getSelection()const range = selection.getRangeAt(0)// 使用DOMParser解析粘貼的HTML內容const parser = new DOMParser()const doc = parser.parseFromString(htmlContent, 'text/html')/** 重要* 處理文本節點,一定要替換掉font節點,* 因為font節點獲取內容會包括了css樣式(比如字體、顏色、大小等等)轉換成字符串的結果* 無論是innerText還是textContent都是一樣的結果,嚴重影響填空題識別*/walkTree(doc.body) // ********重要*********// 直接創建一個div存放,現在無法找到又能在同一行又能保留原先樣式粘貼進去,// 要在原有文字后面直接挨著來需要清除文字樣式,會導致選擇題無法識別let div = document.createElement('div')let childNodes = doc.body.childNodeschildNodes.forEach(node => {if (![Node.ATTRIBUTE_NODE, Node.COMMENT_NODE, Node.DOCUMENT_TYPE_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(node.nodeType)) {div.appendChild(node)}})// 移除所有的img標簽const imgs = div.querySelectorAll('img')imgs.forEach(img => img.remove())// 更改文字樣式,匹配系統顏色setBodyTextStyle(div, 'var(--el-text-color)', '12px', 'transparent')// 粘貼內容不全時進行修正if (pasteText && div.innerText !== pasteText) {div.innerText = pasteText}// 在原有位置插入處理過的內容range.deleteContents() // 如果要替換選中內容,則先刪除range.insertNode(div) // 插入編輯器range.collapse(true)selection.removeAllRanges()selection.addRange(range)content.value = customEditor.value.innerTextemits('change', customEditor.value.innerText)})
})// 設置文字顏色以及文字大小,匹配系統顏色
const setBodyTextStyle = (body, color, fontSize, bgc) => {// 創建一個遞歸函數來遍歷并設置顏色function setColorRecursively(element) {if (element.nodeType === Node.ELEMENT_NODE) {// 如果是元素節點for (let i = 0; i < element.childNodes.length; i++) {setColorRecursively(element.childNodes[i])}// 設置當前元素的文本顏色if (element.style) {element.style.color = colorelement.style.fontSize = fontSizeelement.style.backgroundColor = bgcelement.style.padding = 0element.style.margin = 0element.style.lineHeight = 20 + 'px'}} else if (element.nodeType === Node.TEXT_NODE) {// 如果是文本節點,查找其父元素并設置顏色if (element.parentElement.style) {element.parentElement.style.color = colorelement.parentElement.style.fontSize = fontSizeelement.parentElement.style.backgroundColor = bgcelement.parentElement.style.padding = 0element.parentElement.style.margin = 0element.parentElement.style.lineHeight = 20 + 'px'}}}// 從body開始遍歷setColorRecursively(body)
}// 清理文本節點,并轉換所有非span元素的文本節點為span,比如是font
const walkTree = (node) => {if (node.nodeType === Node.TEXT_NODE && node.tagName === 'FONT') {var span = document.createElement('span')while (node.firstChild) {span.appendChild(node.firstChild)}node.parentNode.replaceChild(span, node)} else if (node.nodeType === Node.ELEMENT_NODE) {for (var i = 0; i < node.childNodes.length; i++) {walkTree(node.childNodes[i])}}
}const clear = () => {content.value = ''customEditor.value.innerText = ''emits('change', customEditor.value.innerText)
}defineExpose({customEditor,clear
})
</script><style lang="scss" scoped>
.custom-editor {position: relative;width: 100%;padding: 16px;z-index: 10000;.custom-editor-placeholder {position: absolute;top: 16px;left: 16px;color: var(--el-text-color-placeholder);opacity: .5;font-size: 13px;font-size: SourceHanSansCN Regular;z-index: 10001;line-height: 23px;}.custom-editor-content {position: relative;width: 100%;height: 100%;overflow-y: auto;outline: none;border: none;box-shadow: none;z-index: 10002;line-height: 23px;}
}
span {font-size: 12px;font-family: SourceHanSansCN Regular;
}
</style>
組件使用示例
<div class="text-title"><span>輸入區</span><div><bxm-buttonsoplain:disabled="btnDisabled || !inputText"@click="handleClear"><i class="bxm-icon-fail btn-icon"></i>清 空</bxm-button><bxm-buttontype="primary"plain:disabled="btnDisabled || !inputText"@click="handleRecognition"><i class="bxm-icon-switch btn-icon"></i>識 別</bxm-button></div>
</div>
<CustomEditor :data="inputText" ref="editor":disabled="btnDisabled":height="600"style="margin-top: 16px"placeholder="請將試題粘貼在此處,點擊識別,系統將自動解析題干及選項。"@change="(val) => { inputText = val }">
</CustomEditor>
// 識別
const handleRecognition = () => {let data = recognitionMethod(inputText.value, editor.value.customEditor, props.questionType)formDataText.value.bxmAnswerList = JSON.parse(JSON.stringify(data.answerList || []))formDataText.value.bxmQuestionDetail.questionContent = data.questionContentformDataText.value.bxmQuestionDetail.questionType = data.questionTypeformDataText.value.bxmQuestionDetail.questionAnalysis = data.questionAnalysis
}const handleClear = () => {editor.value && editor.value.clear()
}
自己做的試題編輯的組件
<!--根據最新ui設計寫的試題編輯-->
<template><el-form class="edit-question-box":model="formData":disabled="disabled || importLoading"ref="ruleForm"label-width="100px"@submit.native.prevent><div class="tips one-line" v-if="['02'].includes(formData.bxmQuestionDetail.questionType)"><i class="bxm-icon-info tip-icon"></i>提示:填空用連續三個下劃線"_"表示,1個填空題最多設置5個空,若一個空有多個參考答案,匹配任意一個都算正確。</div><el-form-item prop="bxmQuestionDetail.questionContent":key="getUniqueCode()":rules="[{ required: true, message: '請填寫題干', trigger: 'blur' }]"><template #label><div v-if="canChangeType && !qustionId" class="questionContent-custom-label" style="width: 100%"><el-dropdown trigger="click" size="mini":disabled="disabled || importLoading"@command="handlequestionTypeChange($event, '00')"><bxm-tag type="primary" plain style="cursor: pointer">【{{ title }}】</bxm-tag><template #dropdown><el-dropdown-menu><el-dropdown-item v-for="item in questionTypeList" :key="item.key" :command="item.value">{{ item.key }}題</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div><template v-else>【{{ title }}】</template></template><el-input v-model="formData.bxmQuestionDetail.questionContent" type="textarea" :rows="3" placeholder="請輸入題干"></el-input></el-form-item><el-form-item label="【圖片】" prop="fileList"><div class="uplod-box"><el-uploadref="upload"v-model:file-list:="formData.fileList"action="action":multiple="true":auto-upload="false"list-type="picture":show-file-list="false"accept=".jpeg,.jpg,.png":disabled="disabled || importLoading":on-change="handleImageChange":on-preview="handlePictureCardPreview":on-remove="handleRemove"><bxm-button type="primary" :loading="importLoading" :disabled="disabled || importLoading" icon="Upload">選擇文件</bxm-button><template #tip><div class="el-upload__tip">支持上傳多個jpeg、jpg、png文件,單個文件不超過10M。</div></template></el-upload><!-- upload無法回顯 自己畫一個回顯 --><ul class="img-box"><li v-for="(file, index) in formData.fileList":key="index + 'fileList'"class="img-item"><img :src="file.url" alt=""><div class="item-name" @click="handlePictureCardPreview(file)"><el-icon class="item-name-icon"><Document /></el-icon><span class="item-name-label">{{ file.fileName }}</span></div><el-icon v-if="!(disabled || importLoading)" class="item-close" @click="handleRemove(file)"><Close /></el-icon></li></ul></div></el-form-item><div class="edit-question-content"><!-- 單選/多選 --><template v-if="['00', '01'].includes(formData.bxmQuestionDetail.questionType)"><el-form-item v-for="(item, index) in formData.bxmAnswerList" :key="index + getUniqueCode()":prop="`formData.bxmAnswerList.${index}.answerContent`":rules="[{required: false,validate: (rule, value, callback) => handleValidContent(callback, index),trigger: 'blur'}]"><template #label><div class="question-custom-label"><svg-icon icon-class="sort" class="label-icon"></svg-icon><span class="label-title">{{ item.answerTitle }}.</span></div></template><el-inputv-model.trim="item.answerContent"clearableplaceholder="請輸入選項內容"maxlength="50"show-word-limitstyle="width: 50%; margin-right: 10px;"></el-input><!-- 單選 --><template v-if="['00'].includes(formData.bxmQuestionDetail.questionType)"><el-radiov-model="item.answerRight":label="index"@change="changeAnswerRight($event, index)"> </el-radio></template><!-- 多選 --><template v-else><el-checkbox v-model="item.answerRight" true-label="0" false-label="1":disabled="disabled"> </el-checkbox></template><div class="set-answer"><span class="set-answer-title" v-if="showResult(item, index)">設為答案</span> </div><!-- 操作按鈕 --><div class="answer-btn-box"><template v-if="index > 0 && formData.bxmAnswerList.length > 1"><el-tooltip content="上移" placement="top"><bxm-button icon="Top" linktype="primary"@click="upAnswer(index)"></bxm-button></el-tooltip><el-divider direction="vertical" style="margin-left: 2px;"></el-divider></template><template v-if="index < formData.bxmAnswerList.length - 1 && formData.bxmAnswerList.length > 1"><el-tooltip content="下移" placement="top"><bxm-button icon="Bottom" linktype="primary"@click="downAnswer(index)"></bxm-button></el-tooltip><el-divider direction="vertical" style="margin-left: 2px;"></el-divider></template><el-tooltip content="刪除" placement="top"><bxm-button icon="Delete" linktype="primary"@click="delAnswer(index)"></bxm-button></el-tooltip></div></el-form-item></template><!-- 填空 --><template v-else-if="['02'].includes(formData.bxmQuestionDetail.questionType)"><el-form-itemv-for="(item, index) in formData.bxmAnswerList" :key="index + getUniqueCode()":prop="`formData.bxmAnswerList.${index}.answerContent`":rules="[{required: false,validate: (rule, value, callback) => handleValidContent(callback, index),trigger: 'change'}]"><template #label><div class="question-custom-label"><svg-icon icon-class="sort" class="label-icon"></svg-icon><span class="label-title">{{ index + 1 }}.</span></div></template><div class="pack-input-box"><el-tagv-for="(tag, tagIndex) in item.answerMoreSelect":key="tag"type="info":closable="!disabled":disable-transitions="false"style="margin: 2px 4px;"@close="handleCloseTag(tag, index, tagIndex)"><el-tooltip v-if="tag.length > 10" :content="tag" placement="top">{{ tag.slice(0, 10) }}...</el-tooltip><template v-else>{{ tag }}</template></el-tag><el-inputv-if="item.inputVisible"v-model.trim="item.inputValue":ref="`saveTagInput${index}`"class="input-new-tag"style="height: 25px"@keyup.enter.native="handleInputConfirm(index)"@blur="handleInputConfirm(index)"></el-input><el-tooltip v-else content="新增" placement="top"><bxm-buttonicon="Plus" type="primary"linkstyle="margin-left: 10px"@click="showInput(index)"></bxm-button></el-tooltip></div><el-tooltip content="刪除" placement="top"><bxm-button type="primary" icon="delete" linkstyle="margin-left: 10px"@click="delAnswer02(index)"></bxm-button></el-tooltip></el-form-item></template><!-- 判斷 --><template v-else-if="['03'].includes(formData.bxmQuestionDetail.questionType)"><el-form-item><el-radio v-model="item.answerRight" v-for="(item, index) in formData.bxmAnswerList" :key="index":label="index" style="margin-left: 16px"@change="changeAnswerRight($event, index)">{{ item.answerTitle }}<el-icon style="margin-left: 5px"><Check v-if="item.answerTitle === '正確'" /><Close v-else /></el-icon></el-radio></el-form-item></template></div><!-- 添加按鈕 --><bxm-button v-if="['00', '01'].includes(formData.bxmQuestionDetail.questionType)"type="primary"linkicon="Plus"class="radio-add-btn"@click="addAnswer(formData.bxmAnswerList.length - 1)">添加選項</bxm-button><bxm-button v-if="['02'].includes(formData.bxmQuestionDetail.questionType)"type="primary"linkicon="Plus"class="radio-add-btn"@click="addAnswer02">添加答案</bxm-button><div v-if="!['04', '06'].includes(formData.bxmQuestionDetail.questionType)" class="dash-line"></div><div class="edit-question-bottom"><el-form-item v-if="!['04', '06'].includes(formData.bxmQuestionDetail.questionType)" label="答案:" style="margin-bottom: 8px"><template v-if="['00', '01', '03'].includes(formData.bxmQuestionDetail.questionType)">{{ selectedAnswer }}<el-icon style="margin-left: 5px" v-if="formData.bxmQuestionDetail.questionType === '03'"><Check v-if="selectedAnswer === '正確'" /><Close v-else-if="selectedAnswer === '錯誤'" /></el-icon></template><template v-else><span v-for="(item, index) in formData.bxmAnswerList" :key="index + getUniqueCode()"><span class="p-lr-5">{{ index + 1 }}.</span><span v-for="(val, valIndex) in item.answerMoreSelect" :key="valIndex + 'span'"><span class="answer-span p-lr-5">{{ val }}</span><span v-if="valIndex !== item.answerMoreSelect.length - 1" class="p-lr-5">/</span></span></span></template></el-form-item><el-form-item label="解析:" props="questionAnalysis" :key="getUniqueCode()"><el-input v-model="formData.bxmQuestionDetail.questionAnalysis" type="textarea" :rows="8" class="question-content-input"placeholder="請輸入解析"></el-input></el-form-item></div></el-form>
</template><script setup>
import { ref, reactive, onMounted, watch, nextTick, computed, onBeforeMount } from 'vue'
import { BxmMessage, BxmMessageBox } from 'bxm-ui3'
// 下面幾個方法就自己寫寫吧
import { validateIsNull } from 'utils/validate'
import { findItemByValue } from '../../consts/index'
import { checkIndex } from '../consts/index'
const props = defineProps({questionType: {type: String,default: '00'},disabled: {type: Boolean,default: false},data: {type: Object,default: () => {return {}}},qustionId: {type: [String, Number],default: ''},// 是否能夠更改試題類型canChangeType: {type: Boolean,default: false}
})let formData = ref({fileList: [],bxmQuestionDetail: {questBankId: '',questionAnalysis: '',questionContent: '',questionType: '',},bxmAnswerList: [{answerContent: '',answerOrd: '1',answerRight: false,answerTitle: 'A',questDetailId: ''}]
})let questionTypeList = reactive([{value: '00',key: '單選',disabled: false},{value: '01',key: '多選',disabled: false},{value: '02',key: '填空',disabled: false},{value: '03',key: '判斷',disabled: false},{value: '04',key: '簡答',disabled: false},{value: '06',key: '論述',disabled: false}
])
const ruleForm = ref(null)let resultFileList = reactive([])
let importLoading = ref(false)
let dialogImage = ref(false)
let currentIndex = ref(0)
let upload = ref(null)const emits = defineEmits(['change', 'importChange'])const showResult = computed(() => {return (data, index) => {// 單選時if (props.questionType === '00') {return data.answerRight === index} else {return data.answerRight === '0' || formData.value.bxmAnswerList[index].answerRight === '0'}}
})const selectedAnswer = computed(() => {let result = ''if (props.questionType === '03') {formData.value.bxmAnswerList.map(item => {if (item.answerRight !== false) {result = item.answerTitle}})} else if (['00', '01'].includes(props.questionType)) {let filterList = []if (props.questionType === '01') {filterList = formData.value.bxmAnswerList.filter(item => { return item.answerRight && item.answerRight !== '1' }) || []} else {filterList = formData.value.bxmAnswerList.filter((item, index) => { return item.answerRight === index }) || []}result = filterList.map(item => { return item.answerTitle }).join('、')}return result
})const title = computed(() => {return findItemByValue(questionTypeList, formData.value.bxmQuestionDetail.questionType).key + '題'
})const handleValidContent = (callback, index) => {if (['00', '01'].includes(props.questionType)) {let curValue = formData.value.bxmAnswerList[index].answerContentif (!curValue) {return callback('請填寫選項內容')}if (curValue.length > 50) {return callback(`選項${checkIndex(index)}內容長度超出50,請修改`)}let list = formData.value.bxmAnswerList.filter(item => { return item.answerContent === curValue })if (list.length > 1) {return callback('選項不可重復')}} else if (['02'].includes(props.questionType)) {let curAnswer = formData.value.bxmAnswerList[index].answerMoreSelectlet list = Array.isArray(curAnswer) && curAnswer.length ? curAnswer : curAnswer.split(',')let newList = list.filter(item => { return item === formData.value.bxmAnswerList[index].inputValue })if (newList > 0) {return callback('同一空答案不可重復')}}return callback()
}// 處理數據
const handleFormData = (data) => {nextTick(() => {formData.value.bxmQuestionDetail = Object.assign({}, data.bxmQuestionDetail)let bxmAnswers = JSON.parse(JSON.stringify(data.bxmAnswerList ? data.bxmAnswerList : data.bxmAnswers))for (const val of bxmAnswers) {val.answerOrd = parseInt(val.answerOrd)if (['00', '03'].includes(formData.value.bxmQuestionDetail.questionType)) {if (val.answerRight === '0' || val.answerRight === val.answerOrd - 1) { // 為答案val.answerRight !== val.answerOrd - 1 && (val.answerRight = val.answerOrd - 1)} else {val.answerRight = false}} else if (formData.value.bxmQuestionDetail.questionType === '02') {val.answerMoreSelect = Array.isArray(val.answerMoreSelect) ? val.answerMoreSelect : val.answerMoreSelect.split(',')val.inputVisible = falseval.inputValue = ''// 此處用map更新沒有用for實時for(let i = 0; i < val.answerMoreSelect.length; i++) {val.answerMoreSelect[i] = val.answerMoreSelect[i].trim()}}// 去除選項、填空答案前后空格if (val.answerContent) {val.answerContent = val.answerContent.trim()}}// 判斷題如果沒有答案加上默認的if (!bxmAnswers.length && formData.value.bxmQuestionDetail.questionType === '03') {bxmAnswers = [{answerOrd: '1',answerRight: false,answerTitle: '正確'}, {answerOrd: '2',answerRight: false,answerTitle: '錯誤'}]}formData.value.bxmAnswerList = JSON.parse(JSON.stringify(bxmAnswers))// 文件列表處理formData.value.fileList = []resultFileList = []if (Array.isArray(data.fileList) && data.fileList.length) {data.fileList.map(item => {item.url = window.location.origin + '/' + item.filePath;// isOnline: 是否是編輯時后端直接返回的圖片formData.value.fileList.push({ ...item, isOnline: true })// 存儲數據resultFileList.push({isDelete: false,fileName: item.fileName,filePath: item.filePath,isOnline: true})})}})
}// 類型變化
const handlequestionTypeChange = (val, type) => {if (val === formData.value.bxmQuestionDetail.questionType) { return false }if (type === '00') {// 單選/多選相互切換時,加是否保留選項提示if (['00', '01'].includes(formData.value.bxmQuestionDetail.questionType) && ['00', '01'].includes(val)) {BxmMessageBox.confirm('確認更改試題類型?', '提示', {confirmButtonText: '確定',cancelButtonText: '取消',type: 'warning'}).then(() => {formData.value.bxmQuestionDetail.questionType = valBxmMessageBox.confirm('是否保留選項信息,保留時若為多選切換為單選將只保留第一個選中項為答案,若不保留將清空選項信息', '提示', {confirmButtonText: '保留選項',cancelButtonText: '清空選項',type: 'warning'}).then(() => {let selAnswer = formData.value.bxmAnswerList.filter((item, index) => { return val === '00' ? item.answerRight === '0' : item.answerRight === index })let selAnswerOrds = selAnswer.map(item => { return item.answerOrd })formData.value.bxmAnswerList.map((item, index) => {// 多選切換為單選if (val === '00') {selAnswerOrds = selAnswerOrds.length > 1 ? [selAnswerOrds[0]] : selAnswerOrdsitem.answerRight = selAnswerOrds.includes(item.answerOrd) ? index : false} else { // 單選切換為多選item.answerRight = selAnswerOrds.includes(item.answerOrd) ? '0' : '1'}})}).catch(() => {setAnswerData()})}).catch(() => {})} else {BxmMessageBox.confirm('切換試題類型將只保留題干信息,是否繼續?', '提示', {confirmButtonText: '確定',cancelButtonText: '取消',type: 'warning'}).then(() => {formData.value.bxmQuestionDetail.questionType = valsetAnswerData()}).catch(() => {})}} else {formData.value.bxmQuestionDetail.questionType = valsetAnswerData()}}// 設置答案數據
const setAnswerData = () => {// 判斷if (formData.value.bxmQuestionDetail.questionType === '03') {formData.value.bxmAnswerList = [{answerOrd: '1',answerRight: false,answerTitle: '正確'}, {answerOrd: '2',answerRight: false,answerTitle: '錯誤'}]} else if (formData.value.bxmQuestionDetail.questionType === '02') { // 填空formData.value.bxmAnswerList = [{answerOrd: '1',answerMoreSelect: [],answerTitle: '第1空答案',inputVisible: false,inputValue: ''}]} else if (formData.value.bxmQuestionDetail.questionType === '01') { // 多選formData.value.bxmAnswerList = [{answerContent: '',answerOrd: '1',answerRight: '1',answerTitle: 'A',questDetailId: ''}]} else if (formData.value.bxmQuestionDetail.questionType === '00') { // 單選formData.value.bxmAnswerList = [{answerContent: '',answerOrd: '1',answerRight: false,answerTitle: 'A',questDetailId: ''}]}
}watch(() => props.questionType, (val) => {handlequestionTypeChange(val)
}, {immediate: true,deep: true
})watch(() => props.data, (obj) => {handleFormData(Object.assign({}, obj))
}, {immediate: true,deep: true
})watch(() => importLoading.value, (val) => {emits('importChange', val)
}, {immediate: true,deep: true
})// 處理文件刪除
const handleBatchDelFile = async (type) => {if (!resultFileList.length) { return }let list = []if (type === '00') { // 點擊的取消按鈕if (!props.qustionId) { // 新增// 刪除全部文件list = resultFileList} else { // 編輯// 刪除不是后端返回的文件list = resultFileList.filter(item => { return item.isOnline === false })}} else { // 點的確定// 刪除用戶點過刪除的文件list = resultFileList.filter(item => { return item.isDelete === true })}if (list.length) {let params = {filePathList: list.map(item => { return item.filePath })}await deleteFileList(params).catch(() => {})}
}// 當前項往下增加一項
const addAnswer = (index) => {formData.value.bxmAnswerList.splice(index + 1, 0, {answerContent: '',answerOrd: '',answerRight: formData.value.bxmQuestionDetail.questionType === '00' ? false : '1',answerTitle: '',questDetailId: ''})for (const index in formData.value.bxmAnswerList) {const val = formData.value.bxmAnswerList[index]val.answerTitle = checkIndex(parseInt(index))val.answerOrd = parseInt(index) + 1}
}// 將當前項往上提一個
const upAnswer = (index) => {if (index !== 0) {formData.value.bxmAnswerList[index] = formData.value.bxmAnswerList.splice(index - 1, 1, formData.value.bxmAnswerList[index])[0];for (const index in formData.value.bxmAnswerList) {const val = formData.value.bxmAnswerList[index]val.answerTitle = checkIndex(parseInt(index))val.answerOrd = parseInt(index) + 1if (formData.value.bxmQuestionDetail.questionType === '00' && val.answerRight !== false) {val.answerRight = parseInt(index)}}}
}// 刪除當前項
const delAnswer = (index) => {if (formData.value.bxmAnswerList.length !== 1) {formData.value.bxmAnswerList.splice(index, 1)for (const index in formData.value.bxmAnswerList) {const val = formData.value.bxmAnswerList[index]val.answerTitle = checkIndex(parseInt(index))val.answerOrd = parseInt(index) + 1}}
}// 將當前項往下降一個
const downAnswer = (index) => {if (index !== formData.value.bxmAnswerList.length - 1) {formData.value.bxmAnswerList[index] = formData.value.bxmAnswerList.splice(index + 1, 1, formData.value.bxmAnswerList[index])[0];for (const index in formData.value.bxmAnswerList) {const val = formData.value.bxmAnswerList[index]val.answerTitle = checkIndex(parseInt(index))val.answerOrd = parseInt(index) + 1if (formData.value.bxmQuestionDetail.questionType === '00' && val.answerRight !== false) {val.answerRight = parseInt(index)}}}
}// 修改答案值
const changeAnswerRight = (value, index) => {for (const i in formData.value.bxmAnswerList) {formData.value.bxmAnswerList[i].answerRight = false // 未選中的存為false,保存時改為0,選中的改為1}formData.value.bxmAnswerList[index].answerRight = index
}// 填空題增加一個空位
const addAnswer02 = () => {if (formData.value.bxmAnswerList.length < 5) {formData.value.bxmAnswerList.push({answerMoreSelect: [],inputVisible: false,inputValue: ''})reSort()}
}// 填空題刪除一個空位
const delAnswer02 = (index) => {formData.value.bxmAnswerList.splice(index, 1)reSort()
}// 填空題增加或修改后答案重新排序
const reSort = () => {for (const index in formData.value.bxmAnswerList) {const val = formData.value.bxmAnswerList[index]val.answerOrd = parseInt(index) + 1val.answerTitle = `第${parseInt(index) + 1}空答案`}
}// 填空題刪除tag
const handleCloseTag = (tag, index, tagIndex) => {// 原先的有問題// formData.value.bxmAnswerList[index].answerMoreSelect.splice(formData.value.bxmAnswerList.indexOf(tag), 1)// 新的formData.value.bxmAnswerList[index].answerMoreSelect.splice(tagIndex, 1)
}// 顯示新增tag輸入框
const showInput = (index) => {formData.value.bxmAnswerList[index].inputVisible = true
}// 新增tag
const handleInputConfirm = (index) => {const inputValue = formData.value.bxmAnswerList[index].inputValueif (inputValue) {if (formData.value.bxmAnswerList[index].answerMoreSelect.includes(inputValue)) {BxmMessage({type: 'warning',message: '同一空答案中不能有重復項,請修改!'})return}formData.value.bxmAnswerList[index].answerMoreSelect.push(inputValue)}formData.value.bxmAnswerList[index].inputVisible = falseformData.value.bxmAnswerList[index].inputValue = ''
}const resetTemp = () => {formData.value.bxmQuestionDetail = {questBankId: props.libraryId,questionAnalysis: '',questionContent: '',questionType: ''}formData.value.bxmAnswerList = [{answerContent: '',answerOrd: '1',answerRight: false,answerTitle: 'A',questDetailId: ''}]
}// 校驗問題
const validateForm = async () => {let flag = await ruleForm.value.validate()if (flag === true) {let bxmAnswerListNew = JSON.parse(JSON.stringify(formData.value.bxmAnswerList)) // 深拷貝一下,防止修改自身時填空題類型的tag報錯// 校驗題干if (!validateIsNull(formData.value.bxmQuestionDetail.questionContent)) {BxmMessage({type: 'warning',message: '請填寫題干!'})return false}let answerRightValidate = falseif (['00', '01', '03'].includes(formData.value.bxmQuestionDetail.questionType)) {// 單選/多選選項重復校驗if (['00', '01'].includes(formData.value.bxmQuestionDetail.questionType)) {// 選項校驗for (let i = 0; i < bxmAnswerListNew.length; i++) {let msg = handleValidContent((msg) => { return msg }, i)if (msg) {BxmMessage({type: 'warning',message: msg})return false}}let answerContent = [...new Set(bxmAnswerListNew.map(item => { return item.answerContent }))]if (answerContent.length < bxmAnswerListNew.length) {BxmMessage({type: 'warning',message: '選項不可重復!'})return false}}// 校驗判斷題答案是否選擇了答案if (formData.value.bxmQuestionDetail.questionType === '03') {let answerRights = [...new Set(bxmAnswerListNew.map(item => { return item.answerRight }))]if (answerRights.length < bxmAnswerListNew.length) {BxmMessage({type: 'warning',message: '請選擇一個答案!'})return false}}for (const val of bxmAnswerListNew) {if (['00', '01'].includes(formData.value.bxmQuestionDetail.questionType) && !validateIsNull(val.answerContent)) {BxmMessage({type: 'warning',message: '請先將選項內容填寫完整!'})return false}if (formData.value.bxmQuestionDetail.questionType === '00') {if (val.answerRight === false) {val.answerRight = '1'} else {val.answerRight = '0'answerRightValidate = true}}if (formData.value.bxmQuestionDetail.questionType === '03') {if (val.answerRight === false) {val.answerRight = '1'answerRightValidate = true} else {val.answerRight = '0'}}if (formData.value.bxmQuestionDetail.questionType === '01' && val.answerRight === '0') {answerRightValidate = true}}if (!answerRightValidate) {BxmMessage({type: 'warning',message: '請至少選擇一個答案!'})return false}} else if (['02'].includes(formData.value.bxmQuestionDetail.questionType)) {if (bxmAnswerListNew.length === 0) {BxmMessage({type: 'warning',message: '請填寫答案!'})return false}for (const val of bxmAnswerListNew) {if (val.answerMoreSelect.length === 0) {BxmMessage({type: 'warning',message: '請將答案填寫完整!'})return false} else {let answers = [...new Set(val.answerMoreSelect)]if (answers.length < val.answerMoreSelect.length) {BxmMessage({type: 'warning',message: '填空題同一空答案不能有重復,請檢查!'})return false}val.answerMoreSelect = val.answerMoreSelect.join(',')}}} else {bxmAnswerListNew = []bxmAnswerListNew.push({ questionText: formData.value.bxmQuestionDetail.questionAnalysis }) // .replace(/<[^>]+>/g, '')}formData.value.bxmQuestionDetail.questionContent = formData.value.bxmQuestionDetail.questionContent.replace(/<p>/g, '').replace(/<\/p>/g, '')return {bxmQuestionDetail: formData.value.bxmQuestionDetail,bxmAnswerList: bxmAnswerListNew,fileList: formData.value.fileList}}return flag
}const handleContentChange = (html, text) => {formData.value.bxmQuestionDetail.questionContent = text
}// 有關圖片上傳
const handleImageChange = async (file, fileList) => {if (fileList.length) {importLoading.value = truelet type = file.name.split('.').pop()if (!['jpeg', 'jpg', 'png', 'PNG', 'JPG', 'JPEG'].includes(type)) {BxmMessage({type: 'warning',message: `${file.name}圖片格式不支持,請重新選擇!`})useDebounce()// 當前圖片不顯示在頁面upload.value.handleRemove(file)return}let size = Math.ceil(file.size / 1024 / 1024);if (size > 10) {BxmMessage({type: 'warning',message: `${file.name}圖片超過10M,無法上傳,請重新選擇!`})useDebounce()// 當前圖片不顯示在頁面upload.value.handleRemove(file)return}let fileNames = formData.value.fileList.map(item => { return item.fileName });if (fileNames.includes(file.name)) {BxmMessage({type: 'warning',message: `${file.name}圖片已存在,請重新選擇!`})let index = fileList.findIndex(item => { return item.name === uploadFile.name })fileList.splice(index, 1)useDebounce()return}// 多加一次設置loading,保證接口請求時要是禁用狀態!importLoading.value && (importLoading.value = true)const upFormData = new FormData()upFormData.append('file', file.raw)let { fileName, filePath } = await 接口(upFormData).catch(() => {// 當前圖片不顯示在頁面upload.value.handleRemove(file)useDebounce()});formData.value.fileList.push({ fileName, filePath,url: window.location.origin + '/' + filePath,isOnline: false, // 表示剛上傳的圖片})// 存儲數據resultFileList.push({ fileName, filePath, isDelete: false, isOnline: false })useDebounce()}
}// 防抖
const debounce = function (func, delay) {let timer = nullreturn function () {clearTimeout(timer)timer = setTimeout(() => {func()}, delay)}
}const useDebounce = debounce(function () {importLoading.value = false
}, 1000)
// 圖片預覽,這就自己寫寫吧
const handlePictureCardPreview = (uploadFile, index) => {formData.value.fileList.map((item, idx) => {if (item.isOnline) {item.fileName === uploadFile.fileName && (currentIndex.value = index)} else {item.fileName === uploadFile.name && (currentIndex.value = idx)}})dialogImage.value = true
}const handleRemove = (uploadFile) => {let index = nulllet file = nullformData.value.fileList.map((item, itemIndex) => {if (item.isOnline ? item.fileName === uploadFile.fileName : item.fileName === uploadFile.name) {file = itemindex = itemIndex}})let resultFile = nullfile !== null && (resultFile = resultFileList.find(item => item.fileName === file.fileName))resultFile && (resultFile.isDelete = true)// 刪除文件index !== null && (formData.value.fileList.splice(index, 1))}
const handleImageClose = () => {dialogImage.value = falsecurrentIndex.value = 0
}// 清除圖片,重置上傳按鈕
const clearImg = () => {upload.value.clearFiles()formData.value.fileList = []resultFileList = []
}const getFormData = () => {return JSON.parse(JSON.stringify(formData.value))
}defineExpose({resetTemp,validateForm,formData,handleBatchDelFile,clearImg,getFormData
})
</script><style lang="scss" scoped>
$--color-primary: #6383ff;
.p-lr-5 {padding: 0 5px;
}
.edit-question-box {.flex-center {display: flex;align-items: center;}.tips {height: 32px;line-height: 32px;background-color: var(--color-primary-light);color: #6383FF;font-size: 12px;padding: 0 16px;margin-bottom: 10px;.tip-icon {padding: 0 4px;font-size: 14px;}}.edit-question-content {max-height: 200px;overflow-y: auto;.pack-input-box {@extend .flex-center;width: 80%;min-height: 32px;max-height: 155px;border-radius: 4px;border: var(--border-base-3);overflow-x: auto;padding: 0 12px;.input-new-tag {width: 90px;margin-left: 8px;vertical-align: bottom;}:deep(.el-input___inner) {height: 25px}}}.questionContent-custom-label {@extend .flex-center;justify-content: flex-end;width: 100%;height: 32px;}.question-custom-label {@extend .flex-center;width: 100%;text-align: center;.label-icon {margin: 0 16px;font-size: 12px;}.label-title {width: 30px;}}.set-answer {width: 50px;text-align: center;.set-answer-title {font-family: SourceHanSansCN, SourceHanSansCN;font-weight: 400;font-size: 12px;color: var(--color-text-secondary);}}.answer-btn-box {margin-left: 8px;@extend .flex-center;}.radio-add-btn {margin: 0 0 15px 45px;}.dash-line {height: 1px;width: 100%;border-top: 1px dashed #E3E5ED;margin-bottom: 10px;}.edit-question-bottom {background: var(--descriptions-item-bordered-label-background);border-radius: 4px;padding: 15px 15px 15px 0;.answer-span {border-bottom: 1px solid var(--color-text-primary);}}:deep(.el-form-item__label) {font-size: 12px;padding: 0 9px 0 0 !important;color: var(--color-text-primary);}:deep(.el-form-item__label:before) {display: none !important;}:deep(.el-form-item__content) {@extend .flex-center;flex-wrap: nowrap;font-size: 12px;color: var(--color-text-primary);word-break: break-all;}:deep(.el-radio) {margin-right: 0;}:deep(.el-radio__label) {font-size: 12px;color: var(--color-text-regular);}:deep( .question-content-input .el-textarea__inner) {background-color: var(--descriptions-item-bordered-label-background);border: none;box-shadow: none;padding: 0;margin-top: 7.5px;}
}
.uplod-box {display: flex;flex-direction: column;
}
.img-box {display: flex;flex-direction: column;list-style: none;padding: 0;margin: 0;.img-item {display: flex;align-items: center;position: relative;border: var(--border-base-3);border-radius: 6px;margin-top: 10px;padding: 10px;overflow: hidden;&:hover {.item-close {display: block;}}img {display: inline-flex;justify-content: center;align-items: center;width: 70px;height: 70px;object-fit: contain;}.item-name {cursor: pointer;padding-left: 8px;display: flex;align-items: center;.item-name-icon {font-size: 14px;margin-right: 8px;color: var(--color-info);}.item-name-label {overflow: hidden;text-overflow: ellipsis;white-space: nowrap;font-size: 12px;&:hover {color: $--color-primary;}}}.item-close {display: none;position: absolute;right: 5px;top: 5px;cursor: pointer;&:hover {color: $--color-primary;}}}
}
:deep(.el-upload-list__item-file-name) {cursor: pointer;&:hover {color: $--color-primary;}
}
:deep(.el-upload-list__item-file-name) {font-size: 12px;
}
:deep(.el-upload-list),
:deep(.el-upload-list--picture .el-upload-list__item-thumbnail) {background-color: transparent;
}
.img-box {max-height: 214px;overflow-y: auto;
}
</style>
以下是效果圖
單選:
多選:
填空:
判斷:
簡答/論述: