题目引入编辑器
This commit is contained in:
parent
e3366af9ec
commit
ff9a181c99
@ -1,14 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :model-value="visible" title="题目详情" width="720px" @close="handleClose">
|
<el-dialog :model-value="visible" title="题目详情" width="720px" @close="handleClose">
|
||||||
<el-descriptions :column="1" border>
|
<el-descriptions :column="1" border>
|
||||||
<el-descriptions-item label="题目">{{ q?.question_title || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="题目">
|
||||||
|
<div v-if="q?.question_title" v-html="q.question_title"></div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="题型">{{ displayType }}</el-descriptions-item>
|
<el-descriptions-item label="题型">{{ displayType }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="分值">{{ q?.score ?? '-' }}</el-descriptions-item>
|
<el-descriptions-item label="分值">{{ q?.score ?? '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="答案">
|
<el-descriptions-item label="答案">
|
||||||
<span v-if="Array.isArray(answerDisplay)">{{ (answerDisplay || []).join(', ') }}</span>
|
<span v-if="Array.isArray(answerDisplay)">{{ (answerDisplay || []).join(', ') }}</span>
|
||||||
<span v-else>{{ answerDisplay || '-' }}</span>
|
<span v-else>{{ answerDisplay || '-' }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="解析">{{ q?.question_analysis || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="解析">
|
||||||
|
<div v-if="q?.question_analysis" v-html="q.question_analysis"></div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间">{{ formatTime(q?.create_time || q?.created_at) }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatTime(q?.create_time || q?.created_at) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">{{ formatTime(q?.update_time || q?.updated_at) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ formatTime(q?.update_time || q?.updated_at) }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
@ -16,7 +22,11 @@
|
|||||||
<div v-if="Array.isArray(optionsDisplay) && optionsDisplay.length" style="margin-top: 16px;">
|
<div v-if="Array.isArray(optionsDisplay) && optionsDisplay.length" style="margin-top: 16px;">
|
||||||
<el-table :data="optionsDisplay" size="small" style="width: 100%">
|
<el-table :data="optionsDisplay" size="small" style="width: 100%">
|
||||||
<el-table-column prop="label" label="选项" width="80" />
|
<el-table-column prop="label" label="选项" width="80" />
|
||||||
<el-table-column prop="content" label="内容" />
|
<el-table-column label="内容">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-html="row.content"></div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -42,7 +52,11 @@ const typeOptions = ref([])
|
|||||||
|
|
||||||
const q = computed(() => (props.model && (props.model.Question || props.model)) || null)
|
const q = computed(() => (props.model && (props.model.Question || props.model)) || null)
|
||||||
const displayType = computed(() => {
|
const displayType = computed(() => {
|
||||||
const typeValue = String(q.value?.question_type ?? '')
|
// 兼容多种返回字段:question_type / QuestionType,根节点或 Question 内部
|
||||||
|
const raw = q.value
|
||||||
|
? (q.value.question_type ?? q.value.QuestionType)
|
||||||
|
: (props.model?.question_type ?? props.model?.QuestionType)
|
||||||
|
const typeValue = raw != null ? String(raw) : ''
|
||||||
const found = typeOptions.value.find(opt => String(opt.dict_value) === typeValue)
|
const found = typeOptions.value.find(opt => String(opt.dict_value) === typeValue)
|
||||||
return found ? found.dict_label : typeValue
|
return found ? found.dict_label : typeValue
|
||||||
})
|
})
|
||||||
@ -89,4 +103,10 @@ function handleClose() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 固定描述列表第一列宽度 */
|
||||||
|
:deep(.el-descriptions__body) tr td:first-child {
|
||||||
|
/* width: 300px; */
|
||||||
|
min-width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<el-input-number v-model="form.score" :step="1" :min="0" :max="100" :precision="0" />
|
<el-input-number v-model="form.score" :step="1" :min="0" :max="100" :precision="0" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="题目" prop="question_title">
|
<el-form-item label="题目" prop="question_title">
|
||||||
<el-input v-model="form.question_title" placeholder="请输入题目" type="textarea" :rows="3" />
|
<WangEditor v-model="form.question_title" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -67,7 +67,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-form-item label="解析">
|
<el-form-item label="解析">
|
||||||
<el-input v-model="form.question_analysis" type="textarea" :rows="3" placeholder="可选" />
|
<WangEditor v-model="form.question_analysis" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -115,10 +115,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
import { ref, reactive, computed, watch, nextTick, onBeforeUnmount } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getDictItemsByCode } from '@/api/dict'
|
import { getDictItemsByCode } from '@/api/dict'
|
||||||
import { getExamQuestions } from '@/api/exam'
|
import { getExamQuestions } from '@/api/exam'
|
||||||
|
import '@wangeditor/editor/dist/css/style.css'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: { type: Boolean, default: false },
|
visible: { type: Boolean, default: false },
|
||||||
@ -136,6 +137,16 @@ const duplicateDialogVisible = ref(false)
|
|||||||
const duplicateList = ref([])
|
const duplicateList = ref([])
|
||||||
const dialogTitle = computed(() => (props.mode === 'edit' ? '编辑题目' : '新建题目'))
|
const dialogTitle = computed(() => (props.mode === 'edit' ? '编辑题目' : '新建题目'))
|
||||||
|
|
||||||
|
// 富文本编辑器:题目 & 解析
|
||||||
|
const titleEditorRef = ref(null)
|
||||||
|
const titleEditorHtml = ref('')
|
||||||
|
const analysisEditorRef = ref(null)
|
||||||
|
const analysisEditorHtml = ref('')
|
||||||
|
|
||||||
|
const toolbarConfig = {}
|
||||||
|
const titleEditorConfig = { placeholder: '请输入题目' }
|
||||||
|
const analysisEditorConfig = { placeholder: '可选' }
|
||||||
|
|
||||||
const defaultOptions = () => ([
|
const defaultOptions = () => ([
|
||||||
{ key: 'A', label: 'A', content: '' },
|
{ key: 'A', label: 'A', content: '' },
|
||||||
{ key: 'B', label: 'B', content: '' },
|
{ key: 'B', label: 'B', content: '' },
|
||||||
@ -159,6 +170,22 @@ const rules = {
|
|||||||
answer: [{ validator: validateAnswer, trigger: 'change' }]
|
answer: [{ validator: validateAnswer, trigger: 'change' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTitleEditorCreated(editor) {
|
||||||
|
titleEditorRef.value = editor
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTitleEditorChange() {
|
||||||
|
form.question_title = titleEditorHtml.value || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAnalysisEditorCreated(editor) {
|
||||||
|
analysisEditorRef.value = editor
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAnalysisEditorChange() {
|
||||||
|
form.question_analysis = analysisEditorHtml.value || ''
|
||||||
|
}
|
||||||
|
|
||||||
function validateAnswer(rule, value, callback) {
|
function validateAnswer(rule, value, callback) {
|
||||||
if (!showAnswer.value) return callback()
|
if (!showAnswer.value) return callback()
|
||||||
if (isMultipleType.value) {
|
if (isMultipleType.value) {
|
||||||
@ -216,7 +243,7 @@ function initForm() {
|
|||||||
if (props.model) {
|
if (props.model) {
|
||||||
form.question_title = props.model.question_title || ''
|
form.question_title = props.model.question_title || ''
|
||||||
form.question_type = props.model.question_type != null ? String(props.model.question_type) : ''
|
form.question_type = props.model.question_type != null ? String(props.model.question_type) : ''
|
||||||
form.score = props.model.score ?? 5
|
form.score = props.model.score ?? 2
|
||||||
form.question_analysis = props.model.question_analysis || ''
|
form.question_analysis = props.model.question_analysis || ''
|
||||||
form.options = Array.isArray(props.model.options) && props.model.options.length > 0 ? props.model.options.map((o, i) => ({ key: o.key || String.fromCharCode(65 + i), label: o.label || String.fromCharCode(65 + i), content: o.content || '' })) : (isObjective.value ? defaultOptions() : [])
|
form.options = Array.isArray(props.model.options) && props.model.options.length > 0 ? props.model.options.map((o, i) => ({ key: o.key || String.fromCharCode(65 + i), label: o.label || String.fromCharCode(65 + i), content: o.content || '' })) : (isObjective.value ? defaultOptions() : [])
|
||||||
if (Array.isArray(props.model.answer)) {
|
if (Array.isArray(props.model.answer)) {
|
||||||
@ -229,12 +256,16 @@ function initForm() {
|
|||||||
} else {
|
} else {
|
||||||
form.question_title = ''
|
form.question_title = ''
|
||||||
form.question_type = ''
|
form.question_type = ''
|
||||||
form.score = 5
|
form.score = 2
|
||||||
form.question_analysis = ''
|
form.question_analysis = ''
|
||||||
form.options = defaultOptions()
|
form.options = defaultOptions()
|
||||||
form.answer = ''
|
form.answer = ''
|
||||||
multiAnswer.value = []
|
multiAnswer.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步富文本内容
|
||||||
|
titleEditorHtml.value = form.question_title || ''
|
||||||
|
analysisEditorHtml.value = form.question_analysis || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNext() {
|
async function handleNext() {
|
||||||
@ -382,6 +413,15 @@ function highlightMatchedTitle(title) {
|
|||||||
}).join('')
|
}).join('')
|
||||||
return highlightedTitle
|
return highlightedTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (titleEditorRef.value) {
|
||||||
|
titleEditorRef.value.destroy()
|
||||||
|
}
|
||||||
|
if (analysisEditorRef.value) {
|
||||||
|
analysisEditorRef.value.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -417,6 +457,12 @@ function highlightMatchedTitle(title) {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rich-editor {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.duplicate-tip {
|
.duplicate-tip {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user