<script lang="ts" setup>
|
import type { QuestionBankOption, QuestionFormModel } from './types';
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
|
import { createQuestion, getQuestionBanks, getQuestionInfo, updateQuestion } from '#/api/x/tms/question';
|
|
import AnswerSettings from './components/AnswerSettings.vue';
|
import {
|
DIFFICULTY_OPTIONS,
|
QUESTION_TYPE_OPTIONS,
|
SOURCE_OPTIONS,
|
STATUS_OPTIONS,
|
} from './constants';
|
import {
|
createDefaultOptions,
|
hydrateFormFromEntity,
|
parseFormToEntity,
|
resetAnswerForType,
|
validateAnswerByType,
|
} from './parseAnswer';
|
|
defineOptions({ name: 'TmsQuestionForm' });
|
|
const route = useRoute();
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
|
const banks = ref<QuestionBankOption[]>([]);
|
const loading = ref(false);
|
const submitting = ref(false);
|
const formRef = ref();
|
|
const isEdit = computed(() => !!route.params.id && route.params.id !== 'create');
|
|
const dataForm = reactive<QuestionFormModel>({
|
questionType: 'single',
|
bankId: undefined,
|
difficulty: 'normal',
|
sourceType: 'self',
|
bizStatus: 'closed',
|
stem: '',
|
analysis: '',
|
options: createDefaultOptions('single'),
|
judgeAnswer: true,
|
essayAnswer: '',
|
blankMixMode: false,
|
});
|
|
const statusTip = computed(() => STATUS_OPTIONS.find((x) => x.id === dataForm.bizStatus)?.tip);
|
const statusTipColor = computed(() => STATUS_OPTIONS.find((x) => x.id === dataForm.bizStatus)?.tipColor);
|
|
const rules = {
|
questionType: [{ required: true, message: '请选择题型', trigger: 'change' }],
|
bankId: [{ required: true, message: '请选择所属题库', trigger: 'change' }],
|
stem: [{ required: true, message: '请填写题干内容', trigger: 'blur' }],
|
};
|
|
watch(
|
() => dataForm.questionType,
|
(type, prev) => {
|
if (!prev || type === prev) return;
|
Object.assign(dataForm, resetAnswerForType(type));
|
},
|
);
|
|
onMounted(async () => {
|
banks.value = (await getQuestionBanks()) || [];
|
if (isEdit.value) {
|
await loadDetail(String(route.params.id));
|
}
|
});
|
|
async function loadDetail(id: string) {
|
loading.value = true;
|
try {
|
const info = await getQuestionInfo(id);
|
Object.assign(dataForm, hydrateFormFromEntity(info));
|
} catch (e: any) {
|
createMessage.error(e?.message || '加载失败');
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function goList() {
|
router.push('/tms/question');
|
}
|
|
async function handleSubmit() {
|
try {
|
await formRef.value?.validate();
|
} catch {
|
return;
|
}
|
const err = validateAnswerByType(dataForm);
|
if (err) {
|
createMessage.warning(err);
|
return;
|
}
|
submitting.value = true;
|
try {
|
const payload = parseFormToEntity(dataForm);
|
if (isEdit.value) {
|
await updateQuestion(payload as any);
|
createMessage.success('更新成功');
|
} else {
|
await createQuestion(payload as any);
|
createMessage.success('创建成功');
|
}
|
goList();
|
} catch (e: any) {
|
createMessage.error(e?.message || '保存失败');
|
} finally {
|
submitting.value = false;
|
}
|
}
|
|
function handleReset() {
|
if (isEdit.value && dataForm.id) {
|
loadDetail(dataForm.id);
|
return;
|
}
|
Object.assign(dataForm, {
|
questionType: 'single',
|
bankId: undefined,
|
difficulty: 'normal',
|
sourceType: 'self',
|
bizStatus: 'closed',
|
stem: '',
|
analysis: '',
|
...resetAnswerForType('single'),
|
});
|
formRef.value?.clearValidate?.();
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-question-form-page">
|
<div class="jnpf-content-wrapper-center">
|
<div class="jnpf-content-wrapper-content tms-question-form-wrap">
|
<div class="tms-question-form-header">
|
<div>
|
<div class="text-base font-medium">{{ isEdit ? '编辑试题' : '创建试题' }}</div>
|
<div class="mt-1 text-gray-400 text-sm">填写下列基本信息,{{ isEdit ? '保存试题' : '创建试题' }}。</div>
|
</div>
|
<a-space>
|
<a-button @click="goList">管理试题</a-button>
|
<a-button type="primary" :loading="submitting" @click="handleSubmit">
|
{{ isEdit ? '保存试题' : '创建试题' }}
|
</a-button>
|
</a-space>
|
</div>
|
|
<div class="tms-question-form-body">
|
<a-spin :spinning="loading">
|
<a-form
|
ref="formRef"
|
:model="dataForm"
|
:rules="rules"
|
:label-col="{ style: { width: '100px' } }"
|
class="max-w-[1100px] pb-8"
|
>
|
<a-row :gutter="16">
|
<a-col :span="12">
|
<a-form-item label="试题类型" name="questionType">
|
<jnpf-select
|
v-model:value="dataForm.questionType"
|
:options="QUESTION_TYPE_OPTIONS"
|
:allow-clear="false"
|
placeholder="请选择题型"
|
/>
|
</a-form-item>
|
</a-col>
|
<a-col :span="12">
|
<a-form-item label="所属题库" name="bankId">
|
<jnpf-select
|
v-model:value="dataForm.bankId"
|
:options="banks"
|
show-search
|
placeholder="请选择所属题库"
|
/>
|
</a-form-item>
|
</a-col>
|
<a-col :span="12">
|
<a-form-item label="试题难度" name="difficulty">
|
<jnpf-select
|
v-model:value="dataForm.difficulty"
|
:options="DIFFICULTY_OPTIONS"
|
:allow-clear="false"
|
/>
|
</a-form-item>
|
</a-col>
|
<a-col :span="12">
|
<a-form-item label="试题状态" name="bizStatus">
|
<div class="flex items-center gap-3">
|
<jnpf-select
|
v-model:value="dataForm.bizStatus"
|
:options="STATUS_OPTIONS"
|
:allow-clear="false"
|
class="!w-[200px]"
|
/>
|
<span
|
v-if="statusTip"
|
class="text-sm"
|
:class="statusTipColor === 'red' ? 'text-red-500' : 'text-green-600'"
|
>{{ statusTip }}</span>
|
</div>
|
</a-form-item>
|
</a-col>
|
<a-col :span="12">
|
<a-form-item label="试题来源" name="sourceType">
|
<jnpf-select
|
v-model:value="dataForm.sourceType"
|
:options="SOURCE_OPTIONS"
|
:allow-clear="false"
|
/>
|
</a-form-item>
|
</a-col>
|
</a-row>
|
|
<a-form-item label="题干内容" name="stem">
|
<div class="tms-question-editor">
|
<jnpf-editor v-model:value="dataForm.stem" />
|
</div>
|
</a-form-item>
|
|
<a-form-item label="答案设置" required>
|
<AnswerSettings v-model="dataForm" />
|
</a-form-item>
|
|
<a-form-item label="试题解析" name="analysis">
|
<a-textarea v-model:value="dataForm.analysis" :rows="4" placeholder="请输入试题解析(可选)" />
|
</a-form-item>
|
|
<div class="mt-6 flex justify-center gap-3">
|
<a-button type="primary" :loading="submitting" @click="handleSubmit">提交</a-button>
|
<a-button @click="handleReset">重置</a-button>
|
</div>
|
</a-form>
|
</a-spin>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
.tms-question-form-page {
|
height: 100%;
|
min-height: 0;
|
}
|
|
.tms-question-form-wrap {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
min-height: 0;
|
overflow: hidden;
|
background: #fff;
|
padding: 16px;
|
}
|
|
.tms-question-form-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
flex-shrink: 0;
|
margin-bottom: 16px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.tms-question-form-body {
|
flex: 1;
|
min-height: 0;
|
overflow-x: hidden;
|
overflow-y: auto;
|
}
|
|
.tms-question-editor :deep(.tox-tinymce),
|
.tms-question-editor :deep(.tox),
|
.tms-question-editor :deep(.ql-container),
|
.tms-question-editor :deep(.tox-edit-area),
|
.tms-question-editor :deep(iframe) {
|
max-height: 280px;
|
}
|
|
.tms-question-editor :deep(.tox-tinymce) {
|
height: 280px !important;
|
}
|
</style>
|