题目引入编辑器

This commit is contained in:
扫地僧 2025-11-17 23:51:41 +08:00
parent e3366af9ec
commit ff9a181c99
2 changed files with 75 additions and 9 deletions

View File

@ -1,14 +1,20 @@
<template>
<el-dialog :model-value="visible" title="题目详情" width="720px" @close="handleClose">
<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="分值">{{ q?.score ?? '-' }}</el-descriptions-item>
<el-descriptions-item label="答案">
<span v-if="Array.isArray(answerDisplay)">{{ (answerDisplay || []).join(', ') }}</span>
<span v-else>{{ answerDisplay || '-' }}</span>
</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?.update_time || q?.updated_at) }}</el-descriptions-item>
</el-descriptions>
@ -16,7 +22,11 @@
<div v-if="Array.isArray(optionsDisplay) && optionsDisplay.length" style="margin-top: 16px;">
<el-table :data="optionsDisplay" size="small" style="width: 100%">
<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>
</div>
@ -42,7 +52,11 @@ const typeOptions = ref([])
const q = computed(() => (props.model && (props.model.Question || props.model)) || null)
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)
return found ? found.dict_label : typeValue
})
@ -89,4 +103,10 @@ function handleClose() {
</script>
<style scoped>
/* 固定描述列表第一列宽度 */
:deep(.el-descriptions__body) tr td:first-child {
/* width: 300px; */
min-width: 80px;
text-align: right;
}
</style>

View File

@ -24,7 +24,7 @@
<el-input-number v-model="form.score" :step="1" :min="0" :max="100" :precision="0" />
</el-form-item>
<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>
</div>
@ -67,7 +67,7 @@
</template>
<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>
</template>
</el-form>
@ -115,10 +115,11 @@
</template>
<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 { getDictItemsByCode } from '@/api/dict'
import { getExamQuestions } from '@/api/exam'
import '@wangeditor/editor/dist/css/style.css'
const props = defineProps({
visible: { type: Boolean, default: false },
@ -136,6 +137,16 @@ const duplicateDialogVisible = ref(false)
const duplicateList = ref([])
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 = () => ([
{ key: 'A', label: 'A', content: '' },
{ key: 'B', label: 'B', content: '' },
@ -159,6 +170,22 @@ const rules = {
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) {
if (!showAnswer.value) return callback()
if (isMultipleType.value) {
@ -216,7 +243,7 @@ function initForm() {
if (props.model) {
form.question_title = props.model.question_title || ''
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.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)) {
@ -229,12 +256,16 @@ function initForm() {
} else {
form.question_title = ''
form.question_type = ''
form.score = 5
form.score = 2
form.question_analysis = ''
form.options = defaultOptions()
form.answer = ''
multiAnswer.value = []
}
//
titleEditorHtml.value = form.question_title || ''
analysisEditorHtml.value = form.question_analysis || ''
}
async function handleNext() {
@ -382,6 +413,15 @@ function highlightMatchedTitle(title) {
}).join('')
return highlightedTitle
}
onBeforeUnmount(() => {
if (titleEditorRef.value) {
titleEditorRef.value.destroy()
}
if (analysisEditorRef.value) {
analysisEditorRef.value.destroy()
}
})
</script>
<style lang="scss" scoped>
@ -417,6 +457,12 @@ function highlightMatchedTitle(title) {
gap: 12px;
}
.rich-editor {
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 8px 8px;
}
.duplicate-tip {
font-size: 13px;
color: #666;