fix: 修复题目管理页面的 TypeScript 类型错误

This commit is contained in:
QDKF 2025-09-06 01:20:37 +08:00
parent 3d5d34b660
commit e1b32c2c3c
2 changed files with 59 additions and 136 deletions

View File

@ -500,7 +500,7 @@ const createNewQuestion = async (bankId: string) => {
try { try {
// API // API
const questionData = { const questionData = {
parentId: null, // IDnull parentId: undefined, // IDundefined
type: getQuestionTypeNumber(questionForm.type), type: getQuestionTypeNumber(questionForm.type),
content: questionForm.title, content: questionForm.title,
analysis: questionForm.explanation || '', analysis: questionForm.explanation || '',
@ -547,43 +547,6 @@ const createNewQuestion = async (bankId: string) => {
} }
}; };
//
const handleSingleChoiceQuestion = async (questionId: string) => {
try {
//
const optionPromises = questionForm.options.map((option, index) => {
const isCorrect = questionForm.correctAnswer === index ? 1 : 0;
return ExamApi.createQuestionOption({
questionId,
content: option.content,
izCorrent: isCorrect,
orderNo: index + 1
});
});
console.log('🚀 第二步:添加选项,选项数量:', questionForm.options.length);
await Promise.all(optionPromises);
console.log('✅ 选项添加成功');
//
if (questionForm.correctAnswer !== null) {
const correctOption = questionForm.options[questionForm.correctAnswer];
const answerData = {
questionId,
answerText: correctOption.content,
orderNo: 1
};
console.log('🚀 第三步:添加答案:', answerData);
await ExamApi.createQuestionAnswer(answerData);
console.log('✅ 答案添加成功');
}
} catch (error: any) {
console.error('处理单选题失败:', error);
throw error;
}
};
// //
const validateAnswers = (): boolean => { const validateAnswers = (): boolean => {
@ -600,7 +563,7 @@ const validateAnswers = (): boolean => {
message.error('单选题至少需要2个选项'); message.error('单选题至少需要2个选项');
return false; return false;
} }
if (questionForm.options.some(option => !option.content.trim())) { if (questionForm.options.some((option: any) => !option.content.trim())) {
message.error('请填写所有选项的内容'); message.error('请填写所有选项的内容');
return false; return false;
} }
@ -615,7 +578,7 @@ const validateAnswers = (): boolean => {
message.error('多选题至少需要2个选项'); message.error('多选题至少需要2个选项');
return false; return false;
} }
if (questionForm.options.some(option => !option.content.trim())) { if (questionForm.options.some((option: any) => !option.content.trim())) {
message.error('请填写所有选项的内容'); message.error('请填写所有选项的内容');
return false; return false;
} }
@ -631,7 +594,7 @@ const validateAnswers = (): boolean => {
} }
break; break;
case 'fill_blank': case 'fill_blank':
if (questionForm.fillBlankAnswers.length === 0 || questionForm.fillBlankAnswers.every(answer => !answer.content.trim())) { if (questionForm.fillBlankAnswers.length === 0 || questionForm.fillBlankAnswers.every((answer: any) => !answer.content.trim())) {
message.error('请设置填空题的参考答案'); message.error('请设置填空题的参考答案');
return false; return false;
} }

View File

@ -3,94 +3,61 @@
<div class="breadcrumb-section"> <div class="breadcrumb-section">
<n-breadcrumb> <n-breadcrumb>
<n-breadcrumb-item @click="goToQuestionBank"> <n-breadcrumb-item @click="goToQuestionBank">
<n-icon><ChevronBackOutline /></n-icon> <n-icon>
<ChevronBackOutline />
</n-icon>
题库管理 题库管理
</n-breadcrumb-item> </n-breadcrumb-item>
<n-breadcrumb-item>{{ currentBankTitle }}</n-breadcrumb-item> <n-breadcrumb-item>{{ currentBankTitle }}</n-breadcrumb-item>
</n-breadcrumb> </n-breadcrumb>
</div> </div>
<div class="header-section"> <div class="header-section">
<h1 class="title">{{ currentBankTitle }}</h1> <h1 class="title">{{ currentBankTitle }}</h1>
<n-space class="actions-group"> <n-space class="actions-group">
<n-button type="primary" @click="addQuestion">添加试题</n-button> <n-button type="primary" @click="addQuestion">添加试题</n-button>
<n-button ghost @click="importQuestions">导入</n-button> <n-button ghost @click="importQuestions">导入</n-button>
<n-button ghost @click="exportQuestions">导出</n-button> <n-button ghost @click="exportQuestions">导出</n-button>
<n-button type="error" ghost @click="deleteSelected" :disabled="selectedRowKeys.length === 0">删除</n-button> <n-button type="error" ghost @click="deleteSelected"
:disabled="selectedRowKeys.length === 0">删除</n-button>
<n-button @click="setCategoryForSelected" :disabled="selectedRowKeys.length === 0">分类设置</n-button> <n-button @click="setCategoryForSelected" :disabled="selectedRowKeys.length === 0">分类设置</n-button>
<n-select <n-select v-model:value="filters.category" placeholder="分类"
v-model:value="filters.category" :options="[{ label: '全部', value: '' }, ...allCategoryOptions]" style="width: 120px"
placeholder="分类" @update:value="handleFilterChange" />
:options="[{ label: '全部', value: '' }, ...allCategoryOptions]" <n-input v-model:value="filters.keyword" placeholder="请输入想要搜索的内容" style="width: 200px" clearable />
style="width: 120px"
@update:value="handleFilterChange"
/>
<n-input
v-model:value="filters.keyword"
placeholder="请输入想要搜索的内容"
style="width: 200px"
clearable
/>
<n-button type="primary" @click="searchQuestions">搜索</n-button> <n-button type="primary" @click="searchQuestions">搜索</n-button>
</n-space> </n-space>
</div> </div>
<n-data-table <n-data-table ref="tableRef" :columns="columns" :data="questionList" :loading="loading"
ref="tableRef" :pagination="paginationConfig" :row-key="(row: any) => row.id" :checked-row-keys="selectedRowKeys"
:columns="columns" @update:checked-row-keys="handleCheck" class="question-table" :single-line="false" />
:data="questionList"
:loading="loading"
:pagination="paginationConfig"
:row-key="(row: Question) => row.id"
:checked-row-keys="selectedRowKeys"
@update:checked-row-keys="handleCheck"
class="question-table"
:single-line="false"
/>
<!-- 导入弹窗 --> <!-- 导入弹窗 -->
<ImportModal <ImportModal v-model:show="showImportModal" template-name="question_template.xlsx" import-type="question"
v-model:show="showImportModal" @success="handleImportSuccess" @template-download="handleTemplateDownload" />
template-name="question_template.xlsx"
import-type="question"
@success="handleImportSuccess"
@template-download="handleTemplateDownload"
/>
<!-- 分类设置弹窗 --> <!-- 分类设置弹窗 -->
<n-modal <n-modal v-model:show="showCategoryModal" preset="dialog" title="分类设置" style="width: 500px;">
v-model:show="showCategoryModal"
preset="dialog"
title="分类设置"
style="width: 500px;"
>
<div class="category-modal-content"> <div class="category-modal-content">
<div class="selected-info"> <div class="selected-info">
<n-alert type="info" :show-icon="false" style="margin-bottom: 16px;"> <n-alert type="info" :show-icon="false" style="margin-bottom: 16px;">
已选择 {{ selectedRowKeys.length }} 个试题 已选择 {{ selectedRowKeys.length }} 个试题
</n-alert> </n-alert>
</div> </div>
<div class="category-selection"> <div class="category-selection">
<div class="form-item"> <div class="form-item">
<label>选择分类</label> <label>选择分类</label>
<n-select <n-select v-model:value="selectedCategory" :options="allCategoryOptions" placeholder="请选择分类"
v-model:value="selectedCategory" style="width: 100%;" />
:options="allCategoryOptions"
placeholder="请选择分类"
style="width: 100%;"
/>
</div> </div>
<div v-if="showAddCategoryInput" class="form-item"> <div v-if="showAddCategoryInput" class="form-item">
<label>新分类名称</label> <label>新分类名称</label>
<n-space> <n-space>
<n-input <n-input v-model:value="newCategoryName" placeholder="请输入新分类名称" style="width: 200px;"
v-model:value="newCategoryName" @keyup.enter="addNewCategory" />
placeholder="请输入新分类名称"
style="width: 200px;"
@keyup.enter="addNewCategory"
/>
<n-button type="primary" @click="addNewCategory" :disabled="!newCategoryName.trim()"> <n-button type="primary" @click="addNewCategory" :disabled="!newCategoryName.trim()">
添加 添加
</n-button> </n-button>
@ -98,7 +65,7 @@
</div> </div>
</div> </div>
</div> </div>
<template #action> <template #action>
<n-space> <n-space>
<n-button @click="showAddCategoryInput = !showAddCategoryInput" secondary type="info"> <n-button @click="showAddCategoryInput = !showAddCategoryInput" secondary type="info">
@ -113,12 +80,7 @@
</n-modal> </n-modal>
<!-- 分类管理弹窗 --> <!-- 分类管理弹窗 -->
<n-modal <n-modal v-model:show="showCategoryManageModal" preset="dialog" title="分类管理" style="width: 600px;">
v-model:show="showCategoryManageModal"
preset="dialog"
title="分类管理"
style="width: 600px;"
>
<div class="category-manage-content"> <div class="category-manage-content">
<div class="category-header"> <div class="category-header">
<n-space> <n-space>
@ -127,15 +89,11 @@
</n-button> </n-button>
</n-space> </n-space>
</div> </div>
<div v-if="showAddCategoryInManage" class="add-category-section"> <div v-if="showAddCategoryInManage" class="add-category-section">
<n-space> <n-space>
<n-input <n-input v-model:value="newCategoryInManage" placeholder="请输入分类名称" style="width: 200px;"
v-model:value="newCategoryInManage" @keyup.enter="addCategoryInManage" />
placeholder="请输入分类名称"
style="width: 200px;"
@keyup.enter="addCategoryInManage"
/>
<n-button type="primary" @click="addCategoryInManage" :disabled="!newCategoryInManage.trim()"> <n-button type="primary" @click="addCategoryInManage" :disabled="!newCategoryInManage.trim()">
添加 添加
</n-button> </n-button>
@ -144,7 +102,7 @@
</n-button> </n-button>
</n-space> </n-space>
</div> </div>
<div class="category-list"> <div class="category-list">
<n-list> <n-list>
<n-list-item v-for="category in customCategories" :key="category.value"> <n-list-item v-for="category in customCategories" :key="category.value">
@ -163,7 +121,7 @@
</n-list> </n-list>
</div> </div>
</div> </div>
<template #action> <template #action>
<n-space> <n-space>
<n-button @click="closeCategoryManageModal">关闭</n-button> <n-button @click="closeCategoryManageModal">关闭</n-button>
@ -210,6 +168,8 @@ interface Question {
score: number; score: number;
creator: string; creator: string;
createTime: string; createTime: string;
parentId?: string; // parentId
analysis?: string; // analysis
} }
// //
@ -333,7 +293,7 @@ const createColumns = ({
width: 100, width: 100,
align: 'center' as const, align: 'center' as const,
render(row: Question) { render(row: Question) {
const categoryInfo = allCategoryOptions.value.find(cat => cat.value === row.category); const categoryInfo = allCategoryOptions.value.find((cat: any) => cat.value === row.category);
return categoryInfo ? categoryInfo.label : row.category; return categoryInfo ? categoryInfo.label : row.category;
} }
}, },
@ -377,27 +337,27 @@ const createColumns = ({
align: 'center' as const, align: 'center' as const,
render(row: Question) { render(row: Question) {
const buttons: VNode[] = []; const buttons: VNode[] = [];
buttons.push( buttons.push(
h(NButton, { h(NButton, {
size: 'small', size: 'small',
type: 'primary', type: 'primary',
ghost: true, ghost: true,
style: 'margin: 0 3px;', style: 'margin: 0 3px;',
onClick: () => handleAction('编辑', row) onClick: () => handleAction('编辑', row)
}, { default: () => '编辑' }) }, { default: () => '编辑' })
); );
buttons.push( buttons.push(
h(NButton, { h(NButton, {
size: 'small', size: 'small',
type: 'error', type: 'error',
ghost: true, ghost: true,
style: 'margin: 0 3px;', style: 'margin: 0 3px;',
onClick: () => handleAction('删除', row) onClick: () => handleAction('删除', row)
}, { default: () => '删除' }) }, { default: () => '删除' })
); );
return h(NSpace, {}, { default: () => buttons }); return h(NSpace, {}, { default: () => buttons });
} }
} }
@ -420,7 +380,7 @@ const generateMockData = (): Question[] => {
const mockData: Question[] = []; const mockData: Question[] = [];
const types = ['single_choice', 'multiple_choice', 'true_false', 'fill_blank', 'short_answer']; const types = ['single_choice', 'multiple_choice', 'true_false', 'fill_blank', 'short_answer'];
const difficulties = ['easy', 'medium', 'hard']; const difficulties = ['easy', 'medium', 'hard'];
const categories = customCategories.value.map(cat => cat.value); const categories = customCategories.value.map((cat: any) => cat.value);
const creators = ['王建国', '李明', '张三', '刘老师']; const creators = ['王建国', '李明', '张三', '刘老师'];
for (let i = 1; i <= 50; i++) { for (let i = 1; i <= 50; i++) {
@ -492,7 +452,7 @@ const loadQuestions = async () => {
let allData: Question[] = []; let allData: Question[] = [];
// API // API
if (response.data && (response.data.code === 200 || response.data.code === 0) && response.data.result) { if (response.data && typeof response.data === 'object' && 'code' in response.data && (response.data.code === 200 || response.data.code === 0) && 'result' in response.data && response.data.result && Array.isArray(response.data.result)) {
// API // API
allData = response.data.result.map((item: any, index: number) => ({ allData = response.data.result.map((item: any, index: number) => ({
id: item.id || `question_${index}`, id: item.id || `question_${index}`,
@ -746,7 +706,7 @@ const addNewCategory = () => {
} }
// //
const exists = customCategories.value.some(cat => cat.label === trimmedName); const exists = customCategories.value.some((cat: any) => cat.label === trimmedName);
if (exists) { if (exists) {
message.warning('该分类已存在'); message.warning('该分类已存在');
return; return;
@ -775,8 +735,8 @@ const applyCategoryChange = async () => {
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
// //
const selectedCategoryLabel = allCategoryOptions.value.find(cat => cat.value === selectedCategory.value)?.label || selectedCategory.value; const selectedCategoryLabel = allCategoryOptions.value.find((cat: any) => cat.value === selectedCategory.value)?.label || selectedCategory.value;
questionList.value.forEach(question => { questionList.value.forEach((question: any) => {
if (selectedRowKeys.value.includes(question.id)) { if (selectedRowKeys.value.includes(question.id)) {
question.category = selectedCategory.value; question.category = selectedCategory.value;
} }
@ -811,7 +771,7 @@ const addCategoryInManage = () => {
} }
// //
const exists = customCategories.value.some(cat => cat.label === trimmedName); const exists = customCategories.value.some((cat: any) => cat.label === trimmedName);
if (exists) { if (exists) {
message.warning('该分类已存在'); message.warning('该分类已存在');
return; return;
@ -841,13 +801,13 @@ const editCategory = (category: { label: string; value: string }) => {
const deleteCategory = (categoryValue: string) => { const deleteCategory = (categoryValue: string) => {
// 使 // 使
const hasQuestions = questionList.value.some(q => q.category === categoryValue); const hasQuestions = questionList.value.some((q: any) => q.category === categoryValue);
if (hasQuestions) { if (hasQuestions) {
message.warning('该分类下还有试题,不能删除'); message.warning('该分类下还有试题,不能删除');
return; return;
} }
const index = customCategories.value.findIndex(cat => cat.value === categoryValue); const index = customCategories.value.findIndex((cat: any) => cat.value === categoryValue);
if (index > -1) { if (index > -1) {
const categoryName = customCategories.value[index].label; const categoryName = customCategories.value[index].label;
customCategories.value.splice(index, 1); customCategories.value.splice(index, 1);