Jelajahi Sumber

创建题库列表,编题

zhaosongshan 7 bulan lalu
induk
melakukan
89f402a3cc

+ 14 - 1
src/api/question/tQuestionApi.js

@@ -24,4 +24,17 @@ export default {
 	// 获取t_question详情
 	tQuestionDetail(data) {
 		return request('detail', data, 'get')
-	}}
+	},
+	pageList(data){
+		return request('api/admin/question/page', data, 'post')
+	},
+	edit(data){
+		return request('api/admin/question/edit', data)
+	},
+	select(id){
+		return request('api/admin/question/select/'+ id)
+	},
+	deleteQuestion(id){
+		return request('api/admin/question/delete/' + id)
+	}
+}

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

@@ -0,0 +1,62 @@
+<template>
+  <div style="line-height:1.8">
+    <div v-if="qType==1" v-loading="qLoading">
+      <div class="q-title" v-html="question.title"/>
+      <div class="q-content">
+          <span :key="item.id" v-for="item in question.items" class="q-item-contain">
+            <span class="q-item-prefix">{{item.prefix}}</span>
+            <span v-html="item.content" class="q-item-content"></span>
+          </span>
+      </div>
+    </div>
+    <div v-else-if="qType==2" v-loading="qLoading">
+      <div class="q-title" v-html="question.title"/>
+      <div class="q-content">
+          <span :key="item.id" v-for="item in question.items" class="q-item-contain">
+            <span class="q-item-prefix">{{item.prefix}}</span>
+            <span v-html="item.content" class="q-item-content"></span>
+          </span>
+      </div>
+    </div>
+    <div v-else-if="qType==3" v-loading="qLoading">
+      <div class="q-title" v-html="question.title" style="display: inline;margin-right: 10px"/>
+      <span>(</span>
+      <span :key="item.id" v-for="item in question.items">
+        <span v-html="item.content" class="q-item-content"></span>
+       </span>
+      <span>)</span>
+    </div>
+    <div v-else-if="qType==4" v-loading="qLoading">
+      <div class="q-title" v-html="question.title"/>
+    </div>
+    <div v-else-if="qType==5" v-loading="qLoading">
+      <div class="q-title" v-html="question.title"/>
+    </div>
+    <div v-else>
+    </div>
+  </div>
+
+</template>
+
+<script>
+export default {
+  name: 'QuestionShow',
+  props: {
+    question: {
+      type: Object,
+      default: function () {
+        return {}
+      }
+    },
+    qLoading: {
+      type: Boolean,
+      default: false
+    },
+    qType: {
+      type: Number,
+      default: 0
+    }
+  },
+  methods: {}
+}
+</script>

+ 279 - 0
src/views/exm/question/edit/single-choice.vue

@@ -0,0 +1,279 @@
+<template>
+	<div class="app-container">
+		<a-spin :spinning="formLoading" tip="加载中...">
+			<a-form :model="form" ref="formRef" label-width="100px" :rules="rules">
+				<a-form-item label="年级:" prop="gradeLevel" required>
+					<a-select v-model:value="form.gradeLevel" placeholder="年级" :options="gradeLevelOptions"  clearable />
+				</a-form-item>
+				<a-form-item label="学科:" prop="subjectId" required>
+					<a-select v-model:value="form.subjectId" placeholder="学科" :options="susbjectOptions" />
+				</a-form-item>
+				<a-form-item label="题干:" prop="title" required>
+					<a-input v-model="form.title"   @focus="inputClick(form,'title')" />
+					<xn-editor v-model:value="form.title"  :height="150"/>
+				</a-form-item>
+				<a-form-item label="选项:" required>
+					<a-form-item :key="item.prefix" v-for="(item, index) in form.items" label-width="50px" class="question-item-label">
+						<template #label>
+							{{ item.prefix }}
+						</template>
+						<a-input v-model:value="item.prefix" style="width:50px;" />
+						<a-button type="primary" danger class="question-item-remove" @click="questionItemRemove(index)">删除</a-button>
+						<xn-editor v-model:value="item.content"  :height="100"/>
+					</a-form-item>
+				</a-form-item>
+				<a-form-item label="解析:" prop="analyze" required>
+					<xn-editor v-model:value="form.analyze" :height="150"/>
+				</a-form-item>
+				<a-form-item label="分数:" prop="score" required>
+					<a-input-number v-model:value="form.score" :precision="1" :step="1" :max="100" />
+				</a-form-item>
+				<a-form-item label="难度:" required>
+					<a-rate v-model:value="form.difficult" class="question-item-rate" />
+				</a-form-item>
+				<a-form-item label="正确答案:" prop="correct" required>
+					<a-radio-group v-model:value="form.correct">
+						<a-radio v-for="item in form.items" :key="item.prefix" :value="item.prefix">{{ item.prefix }}</a-radio>
+					</a-radio-group>
+				</a-form-item>
+				<a-form-item>
+					<a-button type="primary" @click="submitForm" style="margin-right: 10px;">提交</a-button>
+					<a-button @click="resetForm" style="margin-right: 10px;">重置</a-button>
+					<a-button type="success" @click="questionItemAdd" style="margin-right: 10px;">添加选项</a-button>
+					<a-button type="success" @click="showQuestion">预览</a-button>
+				</a-form-item>
+			</a-form>
+		</a-spin>
+
+		<a-modal
+			v-model:visible="richEditor.dialogVisible"
+			:footer="null"
+			:body-style="{ padding: '0' }"
+			:width="800"
+			centered
+			destroy-on-close
+		>
+			<Ueditor @ready="editorReady"/>
+			<a-button type="primary" @click="editorConfirm">确 定</a-button>
+			<a-button @click="richEditor.dialogVisible = false">取 消</a-button>
+		</a-modal>
+
+		<a-modal
+			v-model:visible="questionShow.dialog"
+			:footer="null"
+			:body-style="{ padding: '0' }"
+			:width="800"
+			centered
+			destroy-on-close
+		>
+			<QuestionShow :qType="questionShow.qType" :question="questionShow.question" :qLoading="questionShow.loading" />
+		</a-modal>
+	</div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import questionApi from '@/api/question/tQuestionApi'
+import QuestionShow from '@/views/exm/question/components/Show.vue'
+import tool from "@/utils/tool"
+import XnEditor from '@/components/Editor/index.vue'
+import Ueditor from '@/components/Ueditor/index.vue'
+
+
+const route = useRoute()
+const router = useRouter()
+
+const formLoading = ref(false)
+// 表单引用
+const formRef = ref()
+
+// 数据初始化
+const form = ref({
+	id: null,
+	questionType: 1,
+	gradeLevel: null,
+	subjectId: null,
+	title: '',
+	items: [
+		{ prefix: 'A', content: '' },
+		{ prefix: 'B', content: '' },
+		{ prefix: 'C', content: '' },
+		{ prefix: 'D', content: '' }
+	],
+	analyze: '',
+	correct: '',
+	score: '',
+	difficult: 0
+})
+
+const richEditor = reactive({
+	dialogVisible: false,
+	object: null,
+	parameterName: '',
+	instance: null
+})
+
+const questionShow = reactive({
+	qType: 0,
+	dialog: false,
+	question: null,
+	loading: false
+})
+
+const gradeLevelOptions = tool.dictList('SEMESTER')
+const susbjectOptions = tool.dictList('SUBJECT')
+const subjectFilter = ref(null)
+
+// 校验规则
+const rules = {
+	gradeLevel: [{ required: true, message: '请选择年级', trigger: 'change' }],
+	subjectId: [{ required: true, message: '请选择学科', trigger: 'change' }],
+	title: [{ required: true, message: '请输入题干', trigger: 'blur' }],
+	analyze: [{ required: true, message: '请输入解析', trigger: 'blur' }],
+	score: [{ required: true, message: '请输入分数', trigger: 'blur' }],
+	correct: [{ required: true, message: '请选择正确答案', trigger: 'change' }]
+}
+
+// 生命周期:组件挂载时处理路由参数
+onMounted(() => {
+	const id = route.query.id
+	if (id && parseInt(id) !== 0) {
+		formRef.value?.resetFields()
+		questionApi.select(id).then(re => {
+			Object.assign(form, re.response)
+		})
+	}
+})
+
+// 方法定义
+
+function editorReady(instance) {
+	richEditor.instance = instance
+	let currentContent = richEditor.object[richEditor.parameterName]
+	richEditor.instance.setContent(currentContent)
+	richEditor.instance.focus(true)
+}
+
+function inputClick(object, parameterName) {
+	// 获取当前聚焦的元素并移除焦点
+	const activeElement = document.activeElement
+	if (activeElement && typeof activeElement.blur === 'function') {
+		activeElement.blur()
+	}
+
+	richEditor.object = object
+	richEditor.parameterName = parameterName
+	richEditor.dialogVisible = true
+}
+
+function editorConfirm() {
+	console.log(richEditor)
+	let content = richEditor.instance.getContent()
+	richEditor.object[richEditor.parameterName] = content
+	richEditor.dialogVisible = false
+}
+
+function questionItemRemove(index) {
+	form.value.items.splice(index, 1)
+}
+
+function questionItemAdd() {
+	const items = form.value.items
+	let newLastPrefix
+	console.log('last', items)
+	if (items.length > 0) {
+		let last = items[items.length - 1]
+		newLastPrefix = String.fromCharCode(last.prefix.charCodeAt() + 1)
+	} else {
+		newLastPrefix = 'A'
+	}
+	items.push({ id: null, prefix: newLastPrefix, content: '' })
+}
+
+function submitForm() {
+	formRef.value.validate().then(valid => {
+		if (valid) {
+			formLoading.value = true
+			questionApi.edit(form).then(re => {
+				if (re.code === 1) {
+					store.dispatch('tagsView/delCurrentView').then(() => {
+						router.push('/exam/question/list')
+					})
+				}
+			}).catch(e => {
+				console.error(e)
+			}).finally(() => {
+				formLoading.value = false
+			})
+		} else {
+			return false
+		}
+	})
+}
+
+
+function resetForm() {
+	const lastId = form.id
+	formRef.value.resetFields()
+	Object.assign(form, {
+		id: null,
+		questionType: 1,
+		gradeLevel: null,
+		subjectId: null,
+		title: '',
+		items: [
+			{ prefix: 'A', content: '' },
+			{ prefix: 'B', content: '' },
+			{ prefix: 'C', content: '' },
+			{ prefix: 'D', content: '' }
+		],
+		analyze: '',
+		correct: '',
+		score: '',
+		difficult: 0
+	})
+	form.id = lastId
+}
+
+function showQuestion() {
+	questionShow.dialog = true
+	questionShow.qType = form.value.questionType
+	console.log(form)
+	console.log(questionShow)
+	questionShow.question = form.value
+	form.value.items.forEach((item, index) => {
+		console.log(`选项 ${item.prefix} 的内容:`, item.content)
+	})
+}
+</script>
+
+<style scoped>
+.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-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>

+ 109 - 32
src/views/exm/question/index.vue

@@ -3,19 +3,37 @@
         <a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
             <a-row :gutter="24">
                 <a-col :span="6">
-                    <a-form-item label="QUESTION_TYPE" name="questionType">
-                        <a-input v-model:value="searchFormState.questionType" placeholder="请输入QUESTION_TYPE" />
-                    </a-form-item>
+					<a-form-item label="题型名称:" name="questionType">
+						<a-select
+							v-model:value="searchFormState.questionType"
+							:options="questionTypeOptions"
+							style="width: 70%"
+							placeholder="请选择题型"
+						>
+						</a-select>
+					</a-form-item>
                 </a-col>
                 <a-col :span="6">
-                    <a-form-item label="SUBJECT_ID" name="subjectId">
-                        <a-input v-model:value="searchFormState.subjectId" placeholder="请输入SUBJECT_ID" />
-                    </a-form-item>
+					<a-form-item label="学科名称:" name="subjectId">
+						<a-select
+							v-model:value="searchFormState.subjectId"
+							:options="susbjectOptions"
+							style="width: 70%"
+							placeholder="请选择学科"
+						>
+						</a-select>
+					</a-form-item>
                 </a-col>
                 <a-col :span="6">
-                    <a-form-item label="GRADE_LEVEL" name="gradeLevel">
-                        <a-input v-model:value="searchFormState.gradeLevel" placeholder="请输入GRADE_LEVEL" />
-                    </a-form-item>
+					<a-form-item label="学期名称:" name="gradeLevel">
+						<a-select
+							v-model:value="searchFormState.gradeLevel"
+							:options="gradeLevelOptions"
+							style="width: 70%"
+							placeholder="请选择学期"
+						>
+						</a-select>
+					</a-form-item>
                 </a-col>
                 <a-col :span="6">
                     <a-button type="primary" @click="table.refresh(true)">查询</a-button>
@@ -35,10 +53,26 @@
         >
             <template #operator class="table-operator">
                 <a-space>
-                    <a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('tQuestionAdd')">
-                        <template #icon><plus-outlined /></template>
-                        新增
-                    </a-button>
+					<a-dropdown v-model:visible="isDropdownVisible" :trigger="['click']">
+						<template #overlay>
+							<a-menu slot="overlay" >
+								<a-menu @click="handleMenuClick">
+									<a-menu-item v-for="item in aMenuSelect" :key="item.key">
+										{{ item.label }}
+									</a-menu-item>
+								</a-menu>
+								<a-menu-divider />
+							</a-menu>
+						</template>
+						<a-button type="primary"  v-if="hasPerm('tQuestionAdd')">
+							创建
+							<DownOutlined />
+						</a-button>
+					</a-dropdown>
+<!--                    <a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('tQuestionAdd')">-->
+<!--                        <template #icon><plus-outlined /></template>-->
+<!--                        新增-->
+<!--                    </a-button>-->
                     <xn-batch-delete
                         v-if="hasPerm('tQuestionBatchDelete')"
                         :selectedRowKeys="selectedRowKeys"
@@ -47,6 +81,15 @@
                 </a-space>
             </template>
             <template #bodyCell="{ column, record }">
+				<template v-if="column.dataIndex === 'questionType'">
+					{{ $TOOL.dictTypeData('QUESTION_TYPE', String(record.questionType)) }}
+				</template>
+				<template v-if="column.dataIndex === 'subjectId'">
+					{{ $TOOL.dictTypeData('SUBJECT', String(record.subjectId)) }}
+				</template>
+				<template v-if="column.dataIndex === 'gradeLevel'">
+					{{ $TOOL.dictTypeData('SEMESTER', String(record.gradeLevel)) }}
+				</template>
                 <template v-if="column.dataIndex === 'action'">
                     <a-space>
                         <a @click="formRef.onOpen(record)" v-if="hasPerm('tQuestionEdit')">编辑</a>
@@ -60,52 +103,63 @@
         </s-table>
     </a-card>
     <Form ref="formRef" @successful="table.refresh(true)" />
+	<!-- 模态框部分 -->
+	<a-modal v-model:visible="showModal" title="创建题目" :footer="null" width="80%">
+		<component :is="getComponentByType(selectedQuestionType)" />
+	</a-modal>
 </template>
 
 <script setup name="question">
     import Form from './form.vue'
+	import { DownOutlined } from '@ant-design/icons-vue';
     import tQuestionApi from '@/api/question/tQuestionApi'
+	import SingleChoice from '@/views/exm/question/edit/single-choice.vue'
+	import tool from "@/utils/tool";
     let searchFormState = reactive({})
     const searchFormRef = ref()
     const table = ref()
     const formRef = ref()
     const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
+	const gradeLevelOptions = tool.dictList('SEMESTER')
+	const susbjectOptions = tool.dictList('SUBJECT')
+	const questionTypeOptions = tool.dictList('QUESTION_TYPE')
+
+	let aMenuSelect = questionTypeOptions.map(item => {
+		return {
+			label: item.label,
+			key: item.value
+		}
+	})
+	console.log(aMenuSelect)
+
     const columns = [
+		{
+			title: 'ID',
+			dataIndex: 'id'
+		},
         {
-            title: 'QUESTION_TYPE',
+            title: '题型',
             dataIndex: 'questionType'
         },
         {
-            title: 'SUBJECT_ID',
+            title: '学科',
             dataIndex: 'subjectId'
         },
         {
-            title: 'SCORE',
+            title: '分数',
             dataIndex: 'score'
         },
         {
-            title: 'GRADE_LEVEL',
+            title: '学期',
             dataIndex: 'gradeLevel'
         },
         {
-            title: 'DIFFICULT',
+            title: '难度',
             dataIndex: 'difficult'
         },
         {
-            title: 'CORRECT',
-            dataIndex: 'correct'
-        },
-        {
-            title: 'INFO_TEXT_CONTENT_ID',
-            dataIndex: 'infoTextContentId'
-        },
-        {
-            title: 'STATUS',
-            dataIndex: 'status'
-        },
-        {
-            title: 'DELETED',
-            dataIndex: 'deleted'
+            title: '创建时间',
+            dataIndex: 'createTime'
         },
     ]
     // 操作栏通过权限判断是否显示
@@ -161,4 +215,27 @@
             table.value.clearRefreshSelected()
         })
     }
+	const isDropdownVisible = ref(false)
+	const showModal = ref(false)
+	const selectedQuestionType = ref(null)
+
+	const handleMenuClick = (e) => {
+		const selectedKey = e.key
+		selectedQuestionType.value = selectedKey
+		showModal.value = true
+		isDropdownVisible.value = false // 点击后隐藏下拉菜单
+	}
+	// 根据题型返回对应组件
+	const getComponentByType = (type) => {
+		switch (type) {
+			case '1': // 单选题
+				return SingleChoice
+			case '2': // 多选题
+				return MultipleChoice
+			case '3': // 填空题
+				return FillInBlank
+			default:
+				return null
+		}
+	}
 </script>