feat: 修复
This commit is contained in:
parent
ed8e688422
commit
760290bfb0
@ -289,7 +289,8 @@ import {
|
||||
NCheckbox,
|
||||
NButton,
|
||||
NDivider,
|
||||
NSwitch
|
||||
NSwitch,
|
||||
NFlex
|
||||
} from 'naive-ui';
|
||||
|
||||
// 创建独立的 message API
|
||||
|
@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<span class="tip">
|
||||
{{ selectedRepo ? `已加载题库题目,共${pagination.itemCount}试题` : '请先选择题库' }}
|
||||
{{ selectedRepo ? `已加载题库题目,共${paginationItemCount}试题` : '请先选择题库' }}
|
||||
</span>
|
||||
<n-button type="default" @click="resetFilters" style="margin-right: 8px;">
|
||||
<template #icon>
|
||||
@ -93,8 +93,8 @@
|
||||
|
||||
<!-- 题目列表 -->
|
||||
<div class="question-list-section">
|
||||
<n-data-table ref="tableRef" :columns="columns" :data="questionList" :pagination="pagination"
|
||||
:loading="loading" :row-key="(row: any) => row.id" :checked-row-keys="selectedRowKeys"
|
||||
<n-data-table ref="tableRef" :columns="columns" :data="questionList" :pagination="false"
|
||||
:loading="loading" :row-key="(row) => row.id" :checked-row-keys="selectedRowKeys"
|
||||
@update:checked-row-keys="handleCheck" striped>
|
||||
<template #empty>
|
||||
<div class="empty-state">
|
||||
@ -119,6 +119,14 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-data-table>
|
||||
|
||||
<!-- 独立分页器 -->
|
||||
<div v-if="paginationItemCount > 0" class="pagination-wrapper">
|
||||
<n-pagination v-model:page="paginationPage" v-model:page-size="paginationPageSize"
|
||||
:item-count="paginationItemCount" :page-sizes="[10, 20, 50]" show-size-picker show-quick-jumper
|
||||
:prefix="({ itemCount }) => `共${itemCount}题`" @update:page="handlePageChange"
|
||||
@update:page-size="handlePageSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已选择题目统计 -->
|
||||
@ -248,7 +256,7 @@ const filteredQuestions = computed(() => {
|
||||
|
||||
// 按分类筛选
|
||||
if (filters.value.category) {
|
||||
filtered = filtered.filter(q => q.category === filters.value.category);
|
||||
filtered = filtered.filter((q: QuestionItem) => q.category === filters.value.category);
|
||||
}
|
||||
|
||||
// 按难度筛选
|
||||
@ -259,7 +267,7 @@ const filteredQuestions = computed(() => {
|
||||
'3': '困难'
|
||||
};
|
||||
const targetDifficulty = difficultyMap[filters.value.difficulty];
|
||||
filtered = filtered.filter(q => q.difficulty === targetDifficulty);
|
||||
filtered = filtered.filter((q: QuestionItem) => q.difficulty === targetDifficulty);
|
||||
}
|
||||
|
||||
// 按题型筛选
|
||||
@ -273,13 +281,13 @@ const filteredQuestions = computed(() => {
|
||||
'5': '复合题'
|
||||
};
|
||||
const targetType = typeMap[filters.value.type];
|
||||
filtered = filtered.filter(q => q.type === targetType);
|
||||
filtered = filtered.filter((q: QuestionItem) => q.type === targetType);
|
||||
}
|
||||
|
||||
// 按关键词筛选
|
||||
if (filters.value.keyword) {
|
||||
const keyword = filters.value.keyword.toLowerCase();
|
||||
filtered = filtered.filter(q =>
|
||||
filtered = filtered.filter((q: QuestionItem) =>
|
||||
q.title.toLowerCase().includes(keyword) ||
|
||||
q.creator.toLowerCase().includes(keyword)
|
||||
);
|
||||
@ -369,60 +377,54 @@ const columns = [
|
||||
}
|
||||
];
|
||||
|
||||
// 分页配置
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 20, 50],
|
||||
itemCount: 0,
|
||||
showQuickJumper: true,
|
||||
displayOrder: ['size-picker', 'pages', 'quick-jumper'],
|
||||
onChange: (page: number) => {
|
||||
pagination.value.page = page;
|
||||
updateCurrentPageQuestions();
|
||||
},
|
||||
onUpdatePageSize: (pageSize: number) => {
|
||||
pagination.value.pageSize = pageSize;
|
||||
pagination.value.page = 1;
|
||||
updateCurrentPageQuestions();
|
||||
},
|
||||
prefix: ({ itemCount }: { itemCount: number }) => `共${itemCount}题`
|
||||
});
|
||||
// 分页器状态
|
||||
const paginationPage = ref(1);
|
||||
const paginationPageSize = ref(10);
|
||||
const paginationItemCount = ref(0);
|
||||
|
||||
// 处理复选框选择
|
||||
const handleCheck = (rowKeys: string[]) => {
|
||||
selectedRowKeys.value = rowKeys;
|
||||
};
|
||||
|
||||
// 分页器事件处理
|
||||
const handlePageChange = (page: number) => {
|
||||
paginationPage.value = page;
|
||||
updateCurrentPageQuestions();
|
||||
};
|
||||
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
paginationPageSize.value = pageSize;
|
||||
paginationPage.value = 1;
|
||||
updateCurrentPageQuestions();
|
||||
};
|
||||
|
||||
// 更新当前页的题目
|
||||
const updateCurrentPageQuestions = () => {
|
||||
const filtered = filteredQuestions.value;
|
||||
const startIndex = (pagination.value.page - 1) * pagination.value.pageSize;
|
||||
const endIndex = startIndex + pagination.value.pageSize;
|
||||
const startIndex = (paginationPage.value - 1) * paginationPageSize.value;
|
||||
const endIndex = startIndex + paginationPageSize.value;
|
||||
questionList.value = filtered.slice(startIndex, endIndex);
|
||||
console.log('📄 更新当前页题目 - 页码:', pagination.value.page, '每页:', pagination.value.pageSize, '显示题目数:', questionList.value.length);
|
||||
|
||||
// 更新分页器的总数
|
||||
paginationItemCount.value = filtered.length;
|
||||
|
||||
// 加载当前页题目的详细信息
|
||||
loadCurrentPageDetails();
|
||||
};
|
||||
|
||||
// 处理题库选择变化
|
||||
const handleRepoChange = (repoId: string) => {
|
||||
selectedRepo.value = repoId;
|
||||
filters.value.repoId = repoId;
|
||||
paginationPage.value = 1; // 重置到第一页
|
||||
loadQuestions();
|
||||
};
|
||||
|
||||
// 筛选条件变化处理
|
||||
const handleFilterChange = () => {
|
||||
const filtered = filteredQuestions.value;
|
||||
pagination.value.itemCount = filtered.length;
|
||||
pagination.value.page = 1; // 重置到第一页
|
||||
|
||||
// 计算当前页的题目
|
||||
const startIndex = (pagination.value.page - 1) * pagination.value.pageSize;
|
||||
const endIndex = startIndex + pagination.value.pageSize;
|
||||
questionList.value = filtered.slice(startIndex, endIndex);
|
||||
|
||||
console.log('📊 筛选后分页器更新 - 题目总数:', pagination.value.itemCount);
|
||||
paginationPage.value = 1; // 重置到第一页
|
||||
updateCurrentPageQuestions();
|
||||
};
|
||||
|
||||
// 重置筛选条件
|
||||
@ -450,7 +452,6 @@ const loadCategories = async () => {
|
||||
value: category.name
|
||||
}))
|
||||
];
|
||||
console.log('✅ 加载分类选项成功:', categoryOptions.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分类选项失败:', error);
|
||||
@ -476,7 +477,6 @@ const loadDifficulties = async () => {
|
||||
value: difficulty.id
|
||||
}))
|
||||
];
|
||||
console.log('✅ 加载难度选项成功:', difficultyOptions.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载难度选项失败:', error);
|
||||
@ -496,12 +496,10 @@ const loadRepos = async () => {
|
||||
const response = await ExamApi.getCourseRepoList();
|
||||
if (response.data && response.data.result) {
|
||||
repoList.value = response.data.result;
|
||||
console.log('✅ 加载题库列表成功:', repoList.value);
|
||||
|
||||
// 如果还没有选择题库,自动选择第一个题库
|
||||
if (!selectedRepo.value && repoList.value.length > 0) {
|
||||
selectedRepo.value = repoList.value[0].id;
|
||||
console.log('🔄 自动选择第一个题库:', selectedRepo.value);
|
||||
// 自动加载该题库的题目
|
||||
await loadQuestions();
|
||||
}
|
||||
@ -518,12 +516,11 @@ const loadQuestions = async () => {
|
||||
try {
|
||||
if (selectedRepo.value) {
|
||||
// 根据选择的题库加载题目
|
||||
console.log('🔍 正在加载题库题目:', selectedRepo.value);
|
||||
const response = await ExamApi.getQuestionsByRepo(selectedRepo.value);
|
||||
console.log('✅ 题库题目响应:', response);
|
||||
|
||||
if (response.data && response.data.result && response.data.result.length > 0) {
|
||||
const questions = response.data.result.map((q: Question, index: number) => ({
|
||||
// 优化:先处理基础题目信息,不立即获取选项和答案
|
||||
const questionsWithBasicInfo = response.data.result.map((q: Question, index: number) => ({
|
||||
id: q.id,
|
||||
number: index + 1,
|
||||
title: q.content || '无标题',
|
||||
@ -533,43 +530,133 @@ const loadQuestions = async () => {
|
||||
score: q.score || 5,
|
||||
creator: q.createBy || '未知',
|
||||
createTime: q.createTime ? new Date(q.createTime).toLocaleString() : '未知时间',
|
||||
originalData: q
|
||||
originalData: {
|
||||
...q,
|
||||
options: [], // 初始化为空数组
|
||||
answers: [] // 初始化为空数组
|
||||
}
|
||||
}));
|
||||
allQuestions.value = questions;
|
||||
|
||||
allQuestions.value = questionsWithBasicInfo;
|
||||
|
||||
// 应用筛选条件并更新当前页
|
||||
const filtered = filteredQuestions.value;
|
||||
pagination.value.itemCount = filtered.length;
|
||||
updateCurrentPageQuestions();
|
||||
|
||||
// 计算当前页的题目
|
||||
const startIndex = (pagination.value.page - 1) * pagination.value.pageSize;
|
||||
const endIndex = startIndex + pagination.value.pageSize;
|
||||
questionList.value = filtered.slice(startIndex, endIndex);
|
||||
// 优化:延迟加载选项和答案数据,避免阻塞UI
|
||||
// 只对当前页的题目加载详细数据
|
||||
loadCurrentPageDetails();
|
||||
|
||||
console.log('✅ 题目列表加载成功:', questionList.value);
|
||||
console.log('📊 筛选后题目总数:', filtered.length);
|
||||
} else {
|
||||
// 如果题库没有题目,显示空列表
|
||||
allQuestions.value = [];
|
||||
questionList.value = [];
|
||||
console.log('⚠️ 该题库暂无题目');
|
||||
paginationItemCount.value = 0; // 重置分页器总数
|
||||
}
|
||||
} else {
|
||||
// 如果没有选择题库,显示提示信息
|
||||
allQuestions.value = [];
|
||||
questionList.value = [];
|
||||
console.log('ℹ️ 请先选择题库');
|
||||
paginationItemCount.value = 0; // 重置分页器总数
|
||||
}
|
||||
|
||||
console.log('📊 分页器更新 - 题目总数:', pagination.value.itemCount);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
// 检查是否是"没有题目"的错误
|
||||
if (error?.message?.includes('该题库下没有题目') || error?.message?.includes('该题库不存在')) {
|
||||
// 没有题目不是错误,正常处理
|
||||
allQuestions.value = [];
|
||||
questionList.value = [];
|
||||
paginationItemCount.value = 0;
|
||||
} else {
|
||||
// 真正的错误才显示错误信息
|
||||
console.error('加载题目失败:', error);
|
||||
message.error('加载题目失败');
|
||||
allQuestions.value = [];
|
||||
questionList.value = [];
|
||||
paginationItemCount.value = 0;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:加载当前页题目的详细信息(选项和答案)
|
||||
const loadCurrentPageDetails = async () => {
|
||||
if (!questionList.value || questionList.value.length === 0) return;
|
||||
|
||||
try {
|
||||
// 只对当前页的题目加载选项和答案数据
|
||||
const currentPageQuestions = questionList.value;
|
||||
|
||||
// 使用 Promise.allSettled 避免单个请求失败影响整体
|
||||
const detailPromises = currentPageQuestions.map(async (question: QuestionItem) => {
|
||||
const q = question.originalData;
|
||||
if (!q || !q.id) return question;
|
||||
|
||||
let options: any[] = [];
|
||||
let answers: any[] = [];
|
||||
|
||||
// 如果是选择题或判断题,获取选项数据
|
||||
if (q.type === 0 || q.type === 1 || q.type === 2) {
|
||||
try {
|
||||
const optionsResponse = await ExamApi.getQuestionOptions(q.id);
|
||||
options = processApiResponse(optionsResponse.data);
|
||||
} catch (error) {
|
||||
console.warn(`获取题目 ${q.id} 选项失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是填空题或简答题,获取答案数据
|
||||
if (q.type === 3 || q.type === 4) {
|
||||
try {
|
||||
const answersResponse = await ExamApi.getQuestionAnswers(q.id);
|
||||
answers = processApiResponse(answersResponse.data);
|
||||
} catch (error) {
|
||||
console.warn(`获取题目 ${q.id} 答案失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新题目数据
|
||||
question.originalData = {
|
||||
...q,
|
||||
options: options,
|
||||
answers: answers
|
||||
} as Question;
|
||||
|
||||
return question;
|
||||
});
|
||||
|
||||
// 等待所有详情加载完成
|
||||
await Promise.allSettled(detailPromises);
|
||||
|
||||
// 触发响应式更新
|
||||
allQuestions.value = [...allQuestions.value];
|
||||
|
||||
} catch (error) {
|
||||
console.warn('加载题目详情失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:处理API响应的通用方法
|
||||
const processApiResponse = (data: any): any[] => {
|
||||
if (!data) return [];
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data;
|
||||
} else if (data && Array.isArray(data.result)) {
|
||||
return data.result;
|
||||
} else if (data && data.success && data.result) {
|
||||
if (Array.isArray(data.result)) {
|
||||
return data.result;
|
||||
} else if (data.result && data.result.records) {
|
||||
return data.result.records || [];
|
||||
} else {
|
||||
return [data.result];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
// 导入试题
|
||||
const addNewQuestion = () => {
|
||||
// 创建文件输入元素
|
||||
@ -685,7 +772,7 @@ const exportQuestions = async () => {
|
||||
link.href = url;
|
||||
|
||||
// 获取题库名称作为文件名
|
||||
const repo = repoList.value.find(r => r.id === selectedRepo.value);
|
||||
const repo = repoList.value.find((r: Repo) => r.id === selectedRepo.value);
|
||||
const fileName = repo ? `${repo.title}_题目模板.xlsx` : '题目模板.xlsx';
|
||||
link.download = fileName;
|
||||
|
||||
@ -765,8 +852,8 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-height: 60vh;
|
||||
overflow: hidden;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
@ -807,7 +894,9 @@ onMounted(() => {
|
||||
|
||||
.question-list-section {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.selected-info {
|
||||
@ -817,6 +906,11 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
@ -84,7 +84,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
// defineProps 和 defineEmits 是编译器宏,不需要导入
|
||||
|
||||
interface FillBlankAnswer {
|
||||
content: string;
|
||||
|
@ -96,7 +96,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
// defineProps 和 defineEmits 是编译器宏,不需要导入
|
||||
|
||||
interface Option {
|
||||
content: string;
|
||||
|
@ -67,7 +67,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
// defineProps 和 defineEmits 是编译器宏,不需要导入
|
||||
|
||||
interface Props {
|
||||
modelValue: string;
|
||||
|
@ -73,7 +73,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
// defineProps 和 defineEmits 是编译器宏,不需要导入
|
||||
|
||||
interface Option {
|
||||
content: string;
|
||||
|
@ -81,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
answer: boolean | null;
|
||||
|
@ -138,9 +138,11 @@
|
||||
<div class="question-component-wrapper">
|
||||
<!-- 单选题 -->
|
||||
<SingleChoiceQuestion v-if="subQuestion.type === 'single_choice'"
|
||||
v-model="subQuestion.options!" v-model:correctAnswer="subQuestion.correctAnswer!"
|
||||
v-model="subQuestion.options!" :correctAnswer="subQuestion.correctAnswer"
|
||||
@update:correctAnswer="(val: number | null) => subQuestion.correctAnswer = val"
|
||||
v-model:title="subQuestion.title" v-model:explanation="subQuestion.explanation" />
|
||||
|
||||
|
||||
<!-- 多选题 -->
|
||||
<MultipleChoiceQuestion v-else-if="subQuestion.type === 'multiple_choice'"
|
||||
v-model="subQuestion.options!" v-model:correctAnswers="subQuestion.correctAnswers!"
|
||||
@ -263,8 +265,20 @@
|
||||
<n-button strong type="primary" secondary size="large">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button strong type="primary" size="large" @click="saveExam">
|
||||
保存试卷
|
||||
<n-button strong type="primary" size="large" @click="saveExam" :loading="saving"
|
||||
:disabled="saving">
|
||||
<template #icon v-if="saving">
|
||||
<n-icon>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" class="loading-spinner">
|
||||
<path d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z">
|
||||
<animateTransform attributeName="transform" attributeType="XML"
|
||||
type="rotate" dur="1s" from="0 12 12" to="360 12 12"
|
||||
repeatCount="indefinite" />
|
||||
</path>
|
||||
</svg>
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ saving ? '保存中...' : '保存试卷' }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
@ -364,6 +378,7 @@ enum QuestionType {
|
||||
// 选择题选项接口(适配题型组件)
|
||||
interface ChoiceOption {
|
||||
content: string;
|
||||
isCorrect?: boolean; // 添加可选的正确性标识
|
||||
}
|
||||
|
||||
// 填空题答案接口(适配题型组件)
|
||||
@ -704,7 +719,7 @@ const ensureSubQuestionFields = (subQuestion: SubQuestion): void => {
|
||||
if (!subQuestion.correctAnswers) subQuestion.correctAnswers = [];
|
||||
if (!subQuestion.fillBlanks) subQuestion.fillBlanks = [];
|
||||
if (!subQuestion.subQuestions) subQuestion.subQuestions = [];
|
||||
if (subQuestion.correctAnswer === undefined) subQuestion.correctAnswer = null;
|
||||
if (subQuestion.correctAnswer === undefined || subQuestion.correctAnswer === '' || subQuestion.correctAnswer === null) subQuestion.correctAnswer = null;
|
||||
if (subQuestion.trueFalseAnswer === undefined) subQuestion.trueFalseAnswer = null;
|
||||
if (!subQuestion.textAnswer) subQuestion.textAnswer = '';
|
||||
if (!subQuestion.explanation) subQuestion.explanation = '';
|
||||
@ -1062,11 +1077,12 @@ const openQuestionBankModal = (bigQuestionIndex: number) => {
|
||||
};
|
||||
|
||||
// 处理题库选择确认
|
||||
const handleQuestionBankConfirm = (selectedQuestions: any[]) => {
|
||||
const handleQuestionBankConfirm = async (selectedQuestions: any[]) => {
|
||||
const bigQuestionIndex = currentBigQuestionIndex.value;
|
||||
|
||||
// 将选择的题目转换为当前系统的题目格式并添加到对应大题
|
||||
selectedQuestions.forEach(question => {
|
||||
for (const question of selectedQuestions) {
|
||||
|
||||
const questionType = getQuestionTypeFromString(question.type);
|
||||
const newSubQuestion: SubQuestion = {
|
||||
id: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
@ -1075,26 +1091,95 @@ const handleQuestionBankConfirm = (selectedQuestions: any[]) => {
|
||||
score: question.score,
|
||||
difficulty: question.difficulty,
|
||||
required: 'true',
|
||||
explanation: '', // 添加默认的解析字段
|
||||
explanation: question.originalData?.analysis || '', // 使用原始数据中的答案解析
|
||||
textAnswer: '', // 添加默认的文本答案字段
|
||||
createTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 根据题型初始化不同的字段
|
||||
// 如果有原始数据,尝试加载完整的题目信息
|
||||
if (question.originalData) {
|
||||
// 设置答案解析
|
||||
newSubQuestion.explanation = question.originalData.analysis || '';
|
||||
|
||||
// 根据题型加载选项和答案
|
||||
if (questionType === 'single_choice' || questionType === 'multiple_choice') {
|
||||
|
||||
if (question.originalData.options && question.originalData.options.length > 0) {
|
||||
// 加载选项数据
|
||||
newSubQuestion.options = question.originalData.options.map((option: any) => ({
|
||||
content: option.content || '',
|
||||
isCorrect: option.izCorrent === 1
|
||||
}));
|
||||
|
||||
// 设置正确答案
|
||||
if (questionType === 'single_choice') {
|
||||
const correctOption = question.originalData.options.find((opt: any) => opt.izCorrent === 1);
|
||||
newSubQuestion.correctAnswer = correctOption ? question.originalData.options.indexOf(correctOption) : null;
|
||||
} else if (questionType === 'multiple_choice') {
|
||||
newSubQuestion.correctAnswers = question.originalData.options
|
||||
.map((opt: any, index: number) => opt.izCorrent === 1 ? index : -1)
|
||||
.filter((index: number) => index !== -1);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 如果没有选项数据,使用默认选项
|
||||
newSubQuestion.options = [
|
||||
{ content: '选项A', isCorrect: false },
|
||||
{ content: '选项B', isCorrect: false },
|
||||
{ content: '选项C', isCorrect: false },
|
||||
{ content: '选项D', isCorrect: false }
|
||||
];
|
||||
newSubQuestion.correctAnswer = null;
|
||||
newSubQuestion.correctAnswers = [];
|
||||
}
|
||||
} else if (questionType === 'true_false') {
|
||||
// 判断题的处理
|
||||
if (question.originalData.options && question.originalData.options.length > 0) {
|
||||
const correctOption = question.originalData.options.find((opt: any) => opt.izCorrent === 1);
|
||||
if (correctOption) {
|
||||
newSubQuestion.trueFalseAnswer = correctOption.content === '正确' || correctOption.content === 'true';
|
||||
}
|
||||
} else {
|
||||
newSubQuestion.trueFalseAnswer = null;
|
||||
}
|
||||
} else if (questionType === 'fill_blank') {
|
||||
// 填空题的处理
|
||||
if (question.originalData.answers && question.originalData.answers.length > 0) {
|
||||
newSubQuestion.fillBlanks = question.originalData.answers.map((answer: any) => ({
|
||||
content: answer.answerText || answer.content || '',
|
||||
score: 1,
|
||||
caseSensitive: false
|
||||
}));
|
||||
} else {
|
||||
newSubQuestion.fillBlanks = [
|
||||
{ content: '', score: 1, caseSensitive: false }
|
||||
];
|
||||
}
|
||||
} else if (questionType === 'short_answer') {
|
||||
// 简答题的处理
|
||||
if (question.originalData.answers && question.originalData.answers.length > 0) {
|
||||
newSubQuestion.textAnswer = question.originalData.answers[0]?.answerText || question.originalData.answers[0]?.content || '';
|
||||
} else {
|
||||
newSubQuestion.textAnswer = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果没有原始数据,使用默认值
|
||||
|
||||
if (questionType === 'single_choice') {
|
||||
newSubQuestion.options = [
|
||||
{ content: '选项A' },
|
||||
{ content: '选项B' },
|
||||
{ content: '选项C' },
|
||||
{ content: '选项D' }
|
||||
{ content: '选项A', isCorrect: false },
|
||||
{ content: '选项B', isCorrect: false },
|
||||
{ content: '选项C', isCorrect: false },
|
||||
{ content: '选项D', isCorrect: false }
|
||||
];
|
||||
newSubQuestion.correctAnswer = null;
|
||||
} else if (questionType === 'multiple_choice') {
|
||||
newSubQuestion.options = [
|
||||
{ content: '选项A' },
|
||||
{ content: '选项B' },
|
||||
{ content: '选项C' },
|
||||
{ content: '选项D' }
|
||||
{ content: '选项A', isCorrect: false },
|
||||
{ content: '选项B', isCorrect: false },
|
||||
{ content: '选项C', isCorrect: false },
|
||||
{ content: '选项D', isCorrect: false }
|
||||
];
|
||||
newSubQuestion.correctAnswers = [];
|
||||
} else if (questionType === 'true_false') {
|
||||
@ -1106,9 +1191,10 @@ const handleQuestionBankConfirm = (selectedQuestions: any[]) => {
|
||||
} else if (questionType === 'short_answer') {
|
||||
newSubQuestion.textAnswer = '';
|
||||
}
|
||||
}
|
||||
|
||||
examForm.questions[bigQuestionIndex].subQuestions.push(newSubQuestion);
|
||||
});
|
||||
}
|
||||
|
||||
// 重新计算总分
|
||||
updateBigQuestionScore(bigQuestionIndex);
|
||||
@ -1152,10 +1238,15 @@ const clearExamForm = () => {
|
||||
}
|
||||
|
||||
// 显示确认对话框
|
||||
const dialogTitle = '确认清除试卷内容';
|
||||
const dialogContent = isEditMode.value
|
||||
? '您正在编辑已保存的试卷,确定要清空当前编辑的所有试卷内容吗?\n\n⚠️ 清空后将删除所有题目内容,但试卷基本信息会保留\n⚠️ 此操作不可撤销,请谨慎操作!'
|
||||
: '您已创建了试卷内容,确定要清空当前编辑的所有试卷内容吗?\n\n⚠️ 清空后将删除所有题目内容,但试卷基本信息会保留\n⚠️ 此操作不可撤销,请谨慎操作!';
|
||||
|
||||
dialog.warning({
|
||||
title: '确认清除',
|
||||
content: '确定要清除所有试卷内容吗?此操作不可撤销!',
|
||||
positiveText: '确定清除',
|
||||
title: dialogTitle,
|
||||
content: dialogContent,
|
||||
positiveText: '确定清空',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
// 重置试卷基本信息
|
||||
@ -1190,6 +1281,9 @@ const clearExamForm = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 保存状态
|
||||
const saving = ref(false);
|
||||
|
||||
// 保存试卷
|
||||
const saveExam = async () => {
|
||||
// 验证数据
|
||||
@ -1228,6 +1322,9 @@ const saveExam = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置保存状态
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
// 准备API数据 - 匹配 /aiol/aiolPaper/add 接口
|
||||
const apiData = {
|
||||
@ -1297,11 +1394,11 @@ const saveExam = async () => {
|
||||
console.error('❌ 无法获取试卷ID,跳过题目保存');
|
||||
}
|
||||
|
||||
dialog.success({
|
||||
title: '保存成功',
|
||||
content: isEditMode.value ? '试卷更新成功!' : '试卷保存成功!',
|
||||
positiveText: '确定',
|
||||
onPositiveClick: () => {
|
||||
// 显示保存成功消息
|
||||
message.success(isEditMode.value ? '试卷更新成功!' : '试卷保存成功!');
|
||||
|
||||
// 延迟一下让用户看到成功消息
|
||||
setTimeout(() => {
|
||||
if (isEditMode.value) {
|
||||
// 编辑模式:保存成功后重新加载数据
|
||||
loadExamDetail(examId.value);
|
||||
@ -1311,15 +1408,14 @@ const saveExam = async () => {
|
||||
// 返回试卷列表页面
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建试卷失败:', error);
|
||||
dialog.error({
|
||||
title: '保存失败',
|
||||
content: '试卷保存失败,请重试',
|
||||
positiveText: '确定'
|
||||
});
|
||||
message.error('试卷保存失败,请重试');
|
||||
} finally {
|
||||
// 无论成功还是失败,都要重置保存状态
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1694,6 +1790,7 @@ const isAutoSaved = ref(false);
|
||||
padding: 12px;
|
||||
margin-bottom: 15px 0;
|
||||
}
|
||||
|
||||
.q-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
@ -1923,6 +2020,47 @@ const isAutoSaved = ref(false);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 加载动画样式 */
|
||||
.loading-spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 保存按钮加载状态样式 */
|
||||
.n-button[loading] {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.n-button[loading]::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
@media (max-width: 1200px) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user