- 新增6种题目类型枚举:逆向接诗句、场景猜诗句、联想对对碰、字词飞花令、诗词常识、换字组成语 - 新增QuestionDrawer组件,支持富文本编辑和拼音转换功能 - 优化CategoryTree组件,增加分类选择本地存储和自动填充题目类型 - 更新question API接口,简化新增和更新题目库的参数传递 - 扩展QuestionRenderer组件,支持新增的题目类型渲染 - 添加多个题目库JSON文件,包含成语、诗词常识、字词飞花令等题目数据 - 调整环境配置,更新开发环境API地址
283 lines
8.8 KiB
Vue
283 lines
8.8 KiB
Vue
<script setup lang="ts">
|
|
import type { FormInst, FormItemRule } from 'naive-ui'
|
|
import Clipboard from 'clipboard'
|
|
import {
|
|
NButton,
|
|
NCard,
|
|
NDrawer,
|
|
NDrawerContent,
|
|
NForm,
|
|
NFormItem,
|
|
NInput,
|
|
NInputNumber,
|
|
NRadio,
|
|
NRadioGroup,
|
|
} from 'naive-ui'
|
|
import { pinyin } from 'pinyin-pro'
|
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
|
import OssImageUpload from '@/components/common/oss-image-upload/index.vue'
|
|
import WangEditor from '@/components/common/wang-editor.vue'
|
|
import { QuestionCategoryEnum, QuestionType } from '@/enum/business'
|
|
import { fetchAddQuestionLibrary, fetchUpdateQuestionLibrary } from '@/service/api/question'
|
|
|
|
const props = defineProps<{
|
|
show: boolean
|
|
operation: 'add' | 'edit'
|
|
questionData?: any
|
|
currentCategory: any
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:show', value: boolean): void
|
|
(e: 'success'): void
|
|
}>()
|
|
|
|
const showQuestionModal = computed({
|
|
get: () => props.show,
|
|
set: val => emit('update:show', val),
|
|
})
|
|
|
|
// 拼音转换工具
|
|
const pinyinTool = ref({
|
|
input: '',
|
|
output: '',
|
|
})
|
|
|
|
// 监听输入变化自动转换
|
|
watch(() => pinyinTool.value.input, (val) => {
|
|
if (val) {
|
|
pinyinTool.value.output = pinyin(val)
|
|
}
|
|
else {
|
|
pinyinTool.value.output = ''
|
|
}
|
|
})
|
|
|
|
// 复制拼音
|
|
const copyBtnRef = ref<any>(null)
|
|
let clipboard: Clipboard | null = null
|
|
|
|
watch(copyBtnRef, (inst) => {
|
|
if (clipboard) {
|
|
clipboard.destroy()
|
|
clipboard = null
|
|
}
|
|
if (inst) {
|
|
const domEl = inst.$el || inst
|
|
clipboard = new Clipboard(domEl)
|
|
clipboard.on('success', () => {
|
|
window?.$message?.success('拼音已复制到剪贴板')
|
|
})
|
|
clipboard.on('error', () => {
|
|
window?.$message?.error('复制失败,请手动复制')
|
|
})
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
clipboard?.destroy()
|
|
})
|
|
|
|
const questionForm = ref({
|
|
name: '',
|
|
answer: '',
|
|
type: QuestionCategoryEnum.ChineseCharacterDictation1 as QuestionCategoryEnum | number,
|
|
imageUrl: '',
|
|
IsGood: 0,
|
|
IsDisabled: false,
|
|
KeyWord: '',
|
|
KeyWordsIndex: 0,
|
|
})
|
|
|
|
const questionFormRef = ref<FormInst | null>(null)
|
|
|
|
const rules = computed(() => {
|
|
const dynamicRules: any = {
|
|
answer: [{ required: true, message: '请输入题目答案', trigger: ['blur'] }],
|
|
type: [{ required: true, message: '请选择题目类型', trigger: ['change'] }],
|
|
}
|
|
|
|
// 题目正文内容和图片至少要有一个
|
|
if (!questionForm.value.imageUrl) {
|
|
dynamicRules.name = [{ required: true, message: '请输入题目正文内容或上传图片', trigger: ['blur'] }]
|
|
}
|
|
|
|
// 只有“联想对对碰”类型才需要关键词
|
|
if (questionForm.value.type === QuestionCategoryEnum.AssociationMatching) {
|
|
dynamicRules.KeyWord = [{ required: true, message: '请输入关键词', trigger: ['blur'] }]
|
|
dynamicRules.KeyWordsIndex = [
|
|
{
|
|
required: true,
|
|
validator: (_rule: FormItemRule, value: number) => {
|
|
return typeof value === 'number'
|
|
},
|
|
message: '请输入关键词索引',
|
|
trigger: ['blur', 'change'],
|
|
},
|
|
]
|
|
}
|
|
|
|
return dynamicRules
|
|
})
|
|
|
|
watch(() => props.show, (val) => {
|
|
if (val) {
|
|
if (props.operation === 'add') {
|
|
questionForm.value = {
|
|
name: '',
|
|
answer: '',
|
|
type: props.currentCategory?.QuestionType || QuestionType.SingleChoice,
|
|
imageUrl: '',
|
|
IsGood: 0,
|
|
IsDisabled: false,
|
|
KeyWord: '',
|
|
KeyWordsIndex: 0,
|
|
}
|
|
pinyinTool.value.input = ''
|
|
pinyinTool.value.output = ''
|
|
}
|
|
else if (props.operation === 'edit' && props.questionData) {
|
|
const question = props.questionData
|
|
questionForm.value = {
|
|
name: question.Name,
|
|
answer: question.Answer,
|
|
type: Number(question.Type),
|
|
KeyWord: question.KeyWord || '',
|
|
KeyWordsIndex: question.KeyWordsIndex || 0,
|
|
imageUrl: question.ImageUrl || '',
|
|
IsGood: question.IsPriority || 0,
|
|
IsDisabled: !!question.IsDisabled,
|
|
}
|
|
pinyinTool.value.input = ''
|
|
pinyinTool.value.output = ''
|
|
}
|
|
}
|
|
})
|
|
|
|
async function submitQuestion() {
|
|
const valid = await questionFormRef.value?.validate()
|
|
|
|
if (!valid) {
|
|
window?.$message?.error('请填写完整信息')
|
|
return
|
|
}
|
|
|
|
const params = {
|
|
...questionForm.value,
|
|
questionId: props.currentCategory.Id,
|
|
// type: String(questionForm.value.type) as Api.Question.QuestionScoreType,
|
|
}
|
|
|
|
if (questionForm.value.type !== QuestionCategoryEnum.AssociationMatching) {
|
|
params.KeyWord = ''
|
|
params.KeyWordsIndex = 0
|
|
}
|
|
|
|
if (props.operation === 'add') {
|
|
const { error } = await fetchAddQuestionLibrary({ ...params, id: 0 })
|
|
if (!error) {
|
|
window?.$message?.success('题目添加成功')
|
|
emit('success')
|
|
}
|
|
else {
|
|
window?.$message?.error('添加失败')
|
|
}
|
|
}
|
|
else if (props.operation === 'edit' && props.questionData) {
|
|
const { error } = await fetchUpdateQuestionLibrary({ ...params, id: props.questionData.Id })
|
|
if (!error) {
|
|
window?.$message?.success('题目修改成功')
|
|
emit('success')
|
|
}
|
|
else {
|
|
window?.$message?.error('修改失败')
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<NDrawer v-model:show="showQuestionModal" :width="800">
|
|
<NDrawerContent :title="props.operation === 'edit' ? '编辑题目详情' : '新增题目详情'">
|
|
<div class="mb-4">
|
|
<NCard size="small" title="🛠️ 拼音助手 (输入汉字获取带音标拼音)" class="bg-gray-50/50">
|
|
<div class="flex gap-2">
|
|
<NInput v-model:value="pinyinTool.input" placeholder="输入汉字..." class="flex-1" />
|
|
<div class="flex flex-1 items-center justify-between border border-gray-200 rounded bg-white px-3 py-1">
|
|
<span class="text-gray-600">{{ pinyinTool.output || '拼音结果将显示在这里' }}</span>
|
|
<textarea id="pinyinCopyTarget" v-model="pinyinTool.output" class="absolute opacity-0 -z-1" />
|
|
<div v-if="pinyinTool.output" ref="copyBtnRef" data-clipboard-target="#pinyinCopyTarget">
|
|
<NButton size="tiny" type="primary" secondary>
|
|
复制
|
|
</NButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</NCard>
|
|
</div>
|
|
|
|
<NForm ref="questionFormRef" label-placement="top" :rules="rules" :model="questionForm" size="small">
|
|
<NFormItem label="题目正文内容" path="name">
|
|
<div
|
|
v-if="[QuestionCategoryEnum.PoetryComprehension, QuestionCategoryEnum.PoetryCommonKnowledge, QuestionCategoryEnum.ReversePoemMatching, QuestionCategoryEnum.ChineseCharacterDictation1].includes(props.currentCategory?.QuestionType)"
|
|
class="w-full overflow-hidden border border-gray-200 rounded-lg"
|
|
>
|
|
<WangEditor
|
|
v-model="questionForm.name" placeholder="请输入题目内容..." height="300px"
|
|
:exclude-keys="['header1', 'fontSize', 'fontFamily', 'lineHeight', 'justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify', 'group-image', 'group-video', 'insertTable', 'headerSelect']"
|
|
/>
|
|
</div>
|
|
<NInput v-else v-model:value="questionForm.name" type="textarea" placeholder="请输入题目内容" :rows="3" />
|
|
</NFormItem>
|
|
|
|
<NFormItem label="是否优先使用" path="IsGood">
|
|
<NRadioGroup v-model:value="questionForm.IsGood">
|
|
<NRadio :value="0">
|
|
否
|
|
</NRadio>
|
|
<NRadio :value="1">
|
|
是
|
|
</NRadio>
|
|
</NRadioGroup>
|
|
</NFormItem>
|
|
<NFormItem label="是否禁用" path="IsDisabled">
|
|
<NRadioGroup v-model:value="questionForm.IsDisabled">
|
|
<NRadio :value="false">
|
|
否
|
|
</NRadio>
|
|
<NRadio :value="true">
|
|
是
|
|
</NRadio>
|
|
</NRadioGroup>
|
|
</NFormItem>
|
|
<NFormItem label="上传图片">
|
|
<OssImageUpload v-model="questionForm.imageUrl" />
|
|
</NFormItem>
|
|
<template v-if="currentCategory?.QuestionType === QuestionCategoryEnum.AssociationMatching">
|
|
<NFormItem label="关键词" path="KeyWord">
|
|
<NInput v-model:value="questionForm.KeyWord" placeholder="请输入关键词" />
|
|
</NFormItem>
|
|
|
|
<NFormItem label="关键词索引" path="KeyWordsIndex">
|
|
<NInputNumber v-model:value="questionForm.KeyWordsIndex" placeholder="请输入关键词索引" />
|
|
</NFormItem>
|
|
</template>
|
|
|
|
<NFormItem label="参考标准答案" path="answer">
|
|
<NInput v-model:value="questionForm.answer" placeholder="正确答案" />
|
|
</NFormItem>
|
|
</NForm>
|
|
<template #footer>
|
|
<div class="flex justify-end gap-3">
|
|
<NButton @click="showQuestionModal = false">
|
|
取消并返回
|
|
</NButton>
|
|
<NButton type="primary" @click="submitQuestion">
|
|
保存并入库
|
|
</NButton>
|
|
</div>
|
|
</template>
|
|
</NDrawerContent>
|
|
</NDrawer>
|
|
</template>
|