浏览代码

feat(题目编辑): 添加富文本预览功能并优化样式

为题目编辑页面的题干、选项和解析添加富文本预览功能
将公共样式提取到common.less文件
移除重复的样式定义并优化布局
tanshanming 7 月之前
父节点
当前提交
0a7df022ca

+ 1 - 1
src/store/exam.js

@@ -4,7 +4,7 @@ import tool from '@/utils/tool'
 const funPaperType = function (type) {
 	return tool.dictList(type).map((item) => {
 		return {
-			key: Number(item.value),
+			key: item.value,
 			value: item.label
 		}
 	})

+ 0 - 1
src/views/exm/question/components/Show.vue

@@ -37,7 +37,6 @@
 </template>
 
 <script setup>
-	import { computed } from 'vue'
 	const props = defineProps({
 		question: {
 			type: Object,

+ 37 - 27
src/views/exm/question/edit/gap-filling.vue

@@ -23,23 +23,53 @@
 				</a-select>
 			</a-form-item>
 			<a-form-item label="题干:" name="title" required>
-				<a-input v-model:value="form.title" readonly @click="inputClick(form, 'title')" />
+				<div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
+				<a-input
+					v-else
+					v-model:value="form.title"
+					readonly
+					placeholder="点击编辑题干内容"
+					@click="inputClick(form, 'title')"
+				/>
 			</a-form-item>
 			<a-form-item label="填空答案:" required>
 				<div v-for="item in form.items" :key="item.prefix" class="question-item-label">
+					<div
+						v-if="item.content"
+						class="rich-text-preview question-item-content-input"
+						style="width: 80%"
+						v-html="item.content"
+						@click="inputClick(item, 'content')"
+					></div>
 					<a-input
+						v-else
 						v-model:value="item.content"
 						readonly
+						placeholder="点击编辑答案内容"
 						@click="inputClick(item, 'content')"
 						class="question-item-content-input"
 						style="width: 80%"
 					/>
-					<span class="question-item-span">分数:</span>
-					<a-input-number v-model:value="item.score" :precision="1" :step="1" :max="100" />
+					<p>
+						<span class="question-item-span">分数:</span>
+						<a-input-number v-model:value="item.score" :precision="1" :step="1" :max="100" />
+					</p>
 				</div>
 			</a-form-item>
 			<a-form-item label="解析:" name="analyze" required>
-				<a-input v-model:value="form.analyze" readonly @click="inputClick(form, 'analyze')" />
+				<div
+					v-if="form.analyze"
+					class="rich-text-preview"
+					v-html="form.analyze"
+					@click="inputClick(form, 'analyze')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.analyze"
+					readonly
+					placeholder="点击编辑解析内容"
+					@click="inputClick(form, 'analyze')"
+				/>
 			</a-form-item>
 			<a-form-item label="分数:" name="score" required>
 				<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
@@ -80,8 +110,8 @@
 	import tQuestionApi from '@/api/exam/question/tQuestionApi'
 	import QuestionShow from '../components/Show.vue'
 	import Editor from '@/components/Editor/index.vue'
+	import '../style/common.less'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
-
 	const examStore = useExamStore()
 	const props = defineProps({
 		id: {
@@ -102,7 +132,7 @@
 		correct: '',
 		score: '',
 		difficult: 0,
-		bankType: 1
+		bankType: null
 	})
 	const subjectFilter = ref([])
 	const formLoading = ref(false)
@@ -231,7 +261,7 @@
 			correct: '',
 			score: '',
 			difficult: 0,
-			bankType: 1
+			bankType: null
 		})
 		form.id = lastId
 	}
@@ -243,24 +273,4 @@
 		padding: 24px;
 		border-radius: 8px;
 	}
-	.question-item-label {
-		margin-top: 10px;
-		margin-bottom: 10px !important;
-	}
-	.question-item-content-input {
-		margin-left: 8px;
-		width: 60%;
-		height: 20px;
-	}
-	.question-item-span {
-		vertical-align: middle;
-		font-size: 14px;
-		color: #606266;
-		font-weight: 700;
-		box-sizing: border-box;
-		margin-left: 10px;
-	}
-	.question-item-rate {
-		line-height: 2.5;
-	}
 </style>

+ 31 - 17
src/views/exm/question/edit/multiple-choice.vue

@@ -23,14 +23,30 @@
 				</a-select>
 			</a-form-item>
 			<a-form-item label="题干:" name="title" required>
-				<a-input v-model:value="form.title" readonly @click="inputClick(form, 'title')" />
+				<div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
+				<a-input
+					v-else
+					v-model:value="form.title"
+					readonly
+					placeholder="点击编辑题干内容"
+					@click="inputClick(form, 'title')"
+				/>
 			</a-form-item>
 			<a-form-item label="选项:" required>
 				<div v-for="(item, index) in form.items" :key="item.prefix" class="question-item-label">
 					<a-input v-model:value="item.prefix" style="width: 50px; margin-right: 8px" />
+					<div
+						v-if="item.content"
+						class="rich-text-preview question-item-content-input"
+						style="width: 60%"
+						v-html="item.content"
+						@click="inputClick(item, 'content')"
+					></div>
 					<a-input
+						v-else
 						v-model:value="item.content"
 						readonly
+						placeholder="点击编辑选项内容"
 						@click="inputClick(item, 'content')"
 						class="question-item-content-input"
 						style="width: 60%"
@@ -39,7 +55,19 @@
 				</div>
 			</a-form-item>
 			<a-form-item label="解析:" name="analyze" required>
-				<a-input v-model:value="form.analyze" readonly @click="inputClick(form, 'analyze')" />
+				<div
+					v-if="form.analyze"
+					class="rich-text-preview"
+					v-html="form.analyze"
+					@click="inputClick(form, 'analyze')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.analyze"
+					readonly
+					placeholder="点击编辑解析内容"
+					@click="inputClick(form, 'analyze')"
+				/>
 			</a-form-item>
 			<a-form-item label="分数:" name="score" required>
 				<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
@@ -86,6 +114,7 @@
 	import tQuestionApi from '@/api/exam/question/tQuestionApi'
 	import QuestionShow from '../components/Show.vue'
 	import Editor from '@/components/Editor/index.vue'
+	import '../style/common.less'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
 
 	const examStore = useExamStore()
@@ -244,19 +273,4 @@
 		padding: 24px;
 		border-radius: 8px;
 	}
-	.question-item-label {
-		margin-top: 10px;
-		margin-bottom: 10px !important;
-	}
-	.question-item-remove {
-		margin-left: 20px;
-	}
-	.question-item-content-input {
-		margin-left: 8px;
-		width: 60%;
-		height: 20px;
-	}
-	.question-item-rate {
-		line-height: 2.5;
-	}
 </style>

+ 35 - 7
src/views/exm/question/edit/short-answer.vue

@@ -23,13 +23,44 @@
 				</a-select>
 			</a-form-item>
 			<a-form-item label="题干:" name="title" required>
-				<a-input v-model:value="form.title" readonly @click="inputClick(form, 'title')" />
+				<div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
+				<a-input
+					v-else
+					v-model:value="form.title"
+					readonly
+					placeholder="点击编辑题干内容"
+					@click="inputClick(form, 'title')"
+				/>
 			</a-form-item>
 			<a-form-item label="答案:" name="correct" required>
-				<a-input v-model:value="form.correct" readonly @click="inputClick(form, 'correct')" />
+				<div
+					v-if="form.correct"
+					class="rich-text-preview"
+					v-html="form.correct"
+					@click="inputClick(form, 'correct')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.correct"
+					readonly
+					placeholder="点击编辑答案内容"
+					@click="inputClick(form, 'correct')"
+				/>
 			</a-form-item>
 			<a-form-item label="解析:" name="analyze" required>
-				<a-input v-model:value="form.analyze" readonly @click="inputClick(form, 'analyze')" />
+				<div
+					v-if="form.analyze"
+					class="rich-text-preview"
+					v-html="form.analyze"
+					@click="inputClick(form, 'analyze')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.analyze"
+					readonly
+					placeholder="点击编辑解析内容"
+					@click="inputClick(form, 'analyze')"
+				/>
 			</a-form-item>
 			<a-form-item label="分数:" name="score" required>
 				<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
@@ -65,11 +96,11 @@
 
 <script setup>
 	import { ref, reactive, computed, onMounted } from 'vue'
-	import { message } from 'ant-design-vue'
 	import { useExamStore } from '@/store/exam'
 	import tQuestionApi from '@/api/exam/question/tQuestionApi'
 	import QuestionShow from '../components/Show.vue'
 	import Editor from '@/components/Editor/index.vue'
+	import '../style/common.less'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
 
 	const examStore = useExamStore()
@@ -200,7 +231,4 @@
 		padding: 24px;
 		border-radius: 8px;
 	}
-	.question-item-rate {
-		line-height: 2.5;
-	}
 </style>

+ 31 - 17
src/views/exm/question/edit/single-choice.vue

@@ -23,14 +23,30 @@
 				</a-select>
 			</a-form-item>
 			<a-form-item label="题干:" name="title" required>
-				<a-input v-model:value="form.title" readonly @click="inputClick(form, 'title')" />
+				<div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
+				<a-input
+					v-else
+					v-model:value="form.title"
+					readonly
+					placeholder="点击编辑题干内容"
+					@click="inputClick(form, 'title')"
+				/>
 			</a-form-item>
 			<a-form-item label="选项:" required>
 				<div v-for="(item, index) in form.items" :key="item.prefix" class="question-item-label">
 					<a-input v-model:value="item.prefix" style="width: 50px; margin-right: 8px" />
+					<div
+						v-if="item.content"
+						class="rich-text-preview question-item-content-input"
+						style="width: 60%"
+						v-html="item.content"
+						@click="inputClick(item, 'content')"
+					></div>
 					<a-input
+						v-else
 						v-model:value="item.content"
 						readonly
+						placeholder="点击编辑选项内容"
 						@click="inputClick(item, 'content')"
 						class="question-item-content-input"
 						style="width: 60%"
@@ -39,7 +55,19 @@
 				</div>
 			</a-form-item>
 			<a-form-item label="解析:" name="analyze" required>
-				<a-input v-model:value="form.analyze" readonly @click="inputClick(form, 'analyze')" />
+				<div
+					v-if="form.analyze"
+					class="rich-text-preview"
+					v-html="form.analyze"
+					@click="inputClick(form, 'analyze')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.analyze"
+					readonly
+					placeholder="点击编辑解析内容"
+					@click="inputClick(form, 'analyze')"
+				/>
 			</a-form-item>
 			<a-form-item label="分数:" name="score" required>
 				<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
@@ -86,6 +114,7 @@
 	import tQuestionApi from '@/api/exam/question/tQuestionApi'
 	import QuestionShow from '../components/Show.vue'
 	import Editor from '@/components/Editor/index.vue'
+	import '../style/common.less'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
 
 	const examStore = useExamStore()
@@ -242,19 +271,4 @@
 		padding: 24px;
 		border-radius: 8px;
 	}
-	.question-item-label {
-		margin-top: 10px;
-		margin-bottom: 10px !important;
-	}
-	.question-item-remove {
-		margin-left: 20px;
-	}
-	.question-item-content-input {
-		margin-left: 8px;
-		width: 60%;
-		height: 20px;
-	}
-	.question-item-rate {
-		line-height: 2.5;
-	}
 </style>

+ 31 - 14
src/views/exm/question/edit/true-false.vue

@@ -23,14 +23,30 @@
 				</a-select>
 			</a-form-item>
 			<a-form-item label="题干:" name="title" required>
-				<a-input v-model:value="form.title" readonly @click="inputClick(form, 'title')" />
+				<div v-if="form.title" class="rich-text-preview" v-html="form.title" @click="inputClick(form, 'title')"></div>
+				<a-input
+					v-else
+					v-model:value="form.title"
+					readonly
+					placeholder="点击编辑题干内容"
+					@click="inputClick(form, 'title')"
+				/>
 			</a-form-item>
 			<a-form-item label="选项:" required>
 				<div v-for="item in form.items" :key="item.prefix" class="question-item-label">
 					<a-input v-model:value="item.prefix" style="width: 50px; margin-right: 8px" />
+					<div
+						v-if="item.content"
+						class="rich-text-preview question-item-content-input"
+						style="width: 60%"
+						v-html="item.content"
+						@click="inputClick(item, 'content')"
+					></div>
 					<a-input
+						v-else
 						v-model:value="item.content"
 						readonly
+						placeholder="点击编辑选项内容"
 						@click="inputClick(item, 'content')"
 						class="question-item-content-input"
 						style="width: 60%"
@@ -38,7 +54,19 @@
 				</div>
 			</a-form-item>
 			<a-form-item label="解析:" name="analyze" required>
-				<a-input v-model:value="form.analyze" readonly @click="inputClick(form, 'analyze')" />
+				<div
+					v-if="form.analyze"
+					class="rich-text-preview"
+					v-html="form.analyze"
+					@click="inputClick(form, 'analyze')"
+				></div>
+				<a-input
+					v-else
+					v-model:value="form.analyze"
+					readonly
+					placeholder="点击编辑解析内容"
+					@click="inputClick(form, 'analyze')"
+				/>
 			</a-form-item>
 			<a-form-item label="分数:" name="score" required>
 				<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
@@ -84,6 +112,7 @@
 	import tQuestionApi from '@/api/exam/question/tQuestionApi'
 	import QuestionShow from '../components/Show.vue'
 	import Editor from '@/components/Editor/index.vue'
+	import '../style/common.less'
 	const bankTypeEnum = computed(() => examStore.getBankTypeEnum)
 	const examStore = useExamStore()
 	const props = defineProps({
@@ -219,16 +248,4 @@
 		padding: 24px;
 		border-radius: 8px;
 	}
-	.question-item-label {
-		margin-top: 10px;
-		margin-bottom: 10px !important;
-	}
-	.question-item-content-input {
-		margin-left: 8px;
-		width: 60%;
-		height: 20px;
-	}
-	.question-item-rate {
-		line-height: 2.5;
-	}
 </style>

+ 74 - 0
src/views/exm/question/style/common.less

@@ -0,0 +1,74 @@
+.rich-text-preview {
+	min-height: 32px;
+	padding: 4px 11px;
+	border: 1px solid #d9d9d9;
+	border-radius: 6px;
+	background-color: #fff;
+	cursor: pointer;
+	transition: all 0.2s;
+	line-height: 1.5715;
+	display: flex;
+	overflow: auto;
+
+	&:hover {
+		border-color: #40a9ff;
+	}
+
+	&:focus {
+		border-color: #40a9ff;
+		box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+		outline: 0;
+	}
+
+	// 空内容时的样式
+	&:empty::before {
+		content: '点击编辑内容';
+		color: #bfbfbf;
+	}
+
+	// 处理富文本内容的样式
+	:deep(p) {
+		margin: 0;
+		line-height: 1.5715;
+	}
+
+	:deep(.gapfilling-span) {
+		color: #ff4d4f !important;
+		border-bottom: 3px double #ff4d4f !important;
+		padding: 0 30px !important;
+		margin: 0 5px !important;
+	}
+
+	// 确保富文本内容正确显示
+	:deep(*) {
+		max-width: 100%;
+	}
+}
+
+.question-item-label {
+	margin-top: 10px;
+	margin-bottom: 10px !important;
+	display: flex;
+}
+.question-item-content-input {
+	margin-left: 8px;
+	width: 60%;
+	height: 32px;
+	border-radius: 6px;
+	margin-right: 8px;
+}
+.question-item-span {
+	vertical-align: middle;
+	font-size: 14px;
+	color: #606266;
+	font-weight: 700;
+	box-sizing: border-box;
+	margin-left: 10px;
+}
+.question-item-rate {
+	line-height: 2.5;
+}
+.question-item-remove {
+	margin-left: 20px;
+	margin-top: 3px;
+}