feat:练习讨论
This commit is contained in:
parent
9aa5fbcea0
commit
5519a849a3
@ -1628,6 +1628,16 @@ export class ExamApi {
|
||||
console.log('✅ 发布补考成功:', response)
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* 题目入出库操作
|
||||
*/
|
||||
static async whetherPutStorage(questionIds: string[]): Promise<ApiResponse<any>> {
|
||||
console.log('🚀 题目入出库操作:', questionIds)
|
||||
const response = await ApiRequest.post<any>('/aiol/aiolQuestion/whetherPutStorage', questionIds)
|
||||
console.log('✅ 题目入出库操作成功:', response)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export default ExamApi
|
@ -738,6 +738,10 @@ export interface Question {
|
||||
analysis: string
|
||||
difficulty: number
|
||||
score: number
|
||||
degree: number // 程度:0=了解,1=熟悉,2=掌握
|
||||
ability: number // 能力:0=识记,1=理解,2=应用
|
||||
status: number | null // 状态:0=正常入库,1=未入库
|
||||
knowledge: string | null // 知识点
|
||||
createBy: string
|
||||
createTime: string
|
||||
updateBy: string
|
||||
|
@ -403,15 +403,57 @@ const handleTypeChange = (type: string) => {
|
||||
const goBack = () => {
|
||||
// 根据当前路由上下文决定跳转路径
|
||||
const currentRoute = route.path;
|
||||
const bankId = route.params.bankId;
|
||||
// 路由配置中参数名是 :id,对应题库ID
|
||||
const bankId = route.params.id;
|
||||
|
||||
console.log('🔍 返回操作 - 当前路由:', currentRoute);
|
||||
console.log('🔍 返回操作 - bankId:', bankId);
|
||||
console.log('🔍 返回操作 - 所有路由参数:', route.params);
|
||||
|
||||
// 如果bankId为undefined,尝试从其他地方获取
|
||||
let finalBankId = bankId;
|
||||
if (!finalBankId) {
|
||||
// 尝试从URL路径中提取
|
||||
const pathMatch = currentRoute.match(/add-question\/([^\/]+)/);
|
||||
if (pathMatch) {
|
||||
finalBankId = pathMatch[1];
|
||||
console.log('🔍 从路径提取到bankId:', finalBankId);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从referrer或者sessionStorage获取原始的查询参数
|
||||
const originalQuery = sessionStorage.getItem('questionManagementQuery');
|
||||
let queryParams = '';
|
||||
|
||||
if (originalQuery) {
|
||||
queryParams = originalQuery;
|
||||
sessionStorage.removeItem('questionManagementQuery'); // 使用后清除
|
||||
console.log('🔍 从sessionStorage恢复查询参数:', queryParams);
|
||||
} else {
|
||||
// 如果没有保存的查询参数,尝试从当前路由的query中获取
|
||||
const title = route.query.title || route.query.bankTitle;
|
||||
const createBy = route.query.createBy;
|
||||
|
||||
if (title || createBy) {
|
||||
const params = new URLSearchParams();
|
||||
if (title) params.append('title', title as string);
|
||||
if (createBy) params.append('createBy', createBy as string);
|
||||
queryParams = params.toString();
|
||||
console.log('🔍 从当前路由构建查询参数:', queryParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRoute.includes('/course-editor/')) {
|
||||
// 如果在课程编辑器中,返回课程编辑器的题库管理
|
||||
const courseId = route.params.id || route.params.courseId;
|
||||
router.push(`/teacher/course-editor/${courseId}/question-bank/${bankId}/questions`);
|
||||
const url = `/teacher/course-editor/${courseId}/question-bank/${finalBankId}/questions${queryParams ? '?' + queryParams : ''}`;
|
||||
console.log('🔍 课程编辑器返回URL:', url);
|
||||
router.push(url);
|
||||
} else {
|
||||
// 如果在考试管理中,返回考试管理的题库管理
|
||||
router.push(`/teacher/exam-management/question-bank/${bankId}/questions`);
|
||||
const url = `/teacher/exam-management/question-bank/${finalBankId}/questions${queryParams ? '?' + queryParams : ''}`;
|
||||
console.log('🔍 考试管理返回URL:', url);
|
||||
router.push(url);
|
||||
}
|
||||
};
|
||||
|
||||
@ -474,9 +516,8 @@ const saveQuestion = async () => {
|
||||
|
||||
saving.value = true;
|
||||
|
||||
// 获取题库ID(可能从路由参数或者查询参数获取)
|
||||
// 如果从题库管理页面跳转过来,应该有bankId或者通过其他方式传递
|
||||
let bankId = route.params.bankId as string || route.params.id as string || route.query.bankId as string;
|
||||
// 获取题库ID(路由配置中参数名是 :id,对应题库ID)
|
||||
let bankId = route.params.id as string || route.query.bankId as string;
|
||||
|
||||
if (!bankId) {
|
||||
// 尝试从浏览器历史记录或者本地存储获取
|
||||
@ -1152,24 +1193,11 @@ onMounted(async () => {
|
||||
// 加载分类和难度数据
|
||||
await loadCategoriesAndDifficulties();
|
||||
|
||||
// 如果是编辑模式,加载题目数据
|
||||
// 如果是编辑模式,从API加载题目数据
|
||||
if (isEditMode.value && questionId) {
|
||||
// 优先从路由参数中获取数据
|
||||
if (route.query.questionData) {
|
||||
try {
|
||||
const questionData = JSON.parse(route.query.questionData as string);
|
||||
console.log('📊 从路由参数获取题目数据:', questionData);
|
||||
renderQuestionData(questionData);
|
||||
} catch (error) {
|
||||
console.error('❌ 解析路由参数中的题目数据失败:', error);
|
||||
// 如果解析失败,尝试从API加载
|
||||
console.log('📊 编辑模式,从API加载题目数据');
|
||||
await loadQuestionData(questionId);
|
||||
}
|
||||
} else {
|
||||
// 如果没有路由参数,从API加载
|
||||
await loadQuestionData(questionId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 渲染题目数据
|
||||
|
@ -725,15 +725,21 @@ const deleteSelected = () => {
|
||||
|
||||
// 进入题库(跳转到试题管理页面)
|
||||
const enterQuestionBank = (bankId: string, bankTitle: string) => {
|
||||
// 查找当前题库的创建者信息
|
||||
const currentBank = questionBankList.value.find(bank => bank.id === bankId);
|
||||
const createBy = (currentBank as any)?.createBy || '';
|
||||
|
||||
console.log('🔍 进入题库:', { bankId, bankTitle, createBy });
|
||||
|
||||
// 根据当前路由上下文决定跳转路径
|
||||
const currentRoute = route.path;
|
||||
if (currentRoute.includes('/course-editor/')) {
|
||||
// 如果在课程编辑器中,使用 course-editor 路径
|
||||
const courseId = route.params.id;
|
||||
router.push(`/teacher/course-editor/${courseId}/question-bank/${bankId}/questions?title=${bankTitle}`);
|
||||
router.push(`/teacher/course-editor/${courseId}/question-bank/${bankId}/questions?title=${encodeURIComponent(bankTitle)}&createBy=${createBy}`);
|
||||
} else {
|
||||
// 如果在考试管理中,使用原有路径
|
||||
router.push(`/teacher/exam-management/question-bank/${bankId}/questions?title=${bankTitle}`);
|
||||
router.push(`/teacher/exam-management/question-bank/${bankId}/questions?title=${encodeURIComponent(bankTitle)}&createBy=${createBy}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
<div class="header-section">
|
||||
<h1 class="title">{{ currentBankTitle }}</h1>
|
||||
<n-space class="actions-group">
|
||||
<n-button v-if="canManageStorage" type="success" ghost @click="handleStorageManagement" :disabled="selectedRowKeys.length === 0">入出库</n-button>
|
||||
<n-button type="primary" @click="addQuestion">添加试题</n-button>
|
||||
<n-button ghost @click="importQuestions">导入</n-button>
|
||||
<n-button ghost @click="exportQuestions">导出</n-button>
|
||||
@ -46,6 +47,8 @@
|
||||
@update:checked-row-keys="handleCheck"
|
||||
class="question-table"
|
||||
:single-line="false"
|
||||
:max-height="600"
|
||||
:scroll-x="1200"
|
||||
/>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
@ -174,12 +177,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, h, VNode } from 'vue';
|
||||
import { ref, reactive, computed, onMounted, h } from 'vue';
|
||||
import { NButton, NTag, NSpace, NBreadcrumb, NBreadcrumbItem, NIcon, useMessage } from 'naive-ui';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { ChevronBackOutline } from '@vicons/ionicons5';
|
||||
import ImportModal from '@/components/common/ImportModal.vue';
|
||||
import { ExamApi } from '@/api';
|
||||
import { ExamApi, AuthApi } from '@/api';
|
||||
|
||||
// 消息提示
|
||||
const message = useMessage();
|
||||
@ -191,8 +194,11 @@ const route = useRoute();
|
||||
// 当前题库信息
|
||||
const currentBankId = computed(() => route.params.bankId as string);
|
||||
const currentBankTitle = computed(() => route.query.title as string);
|
||||
const currentBankCreateBy = computed(() => route.query.createBy as string);
|
||||
|
||||
const currentBankName = ref('加载中...');
|
||||
const currentUser = ref<string>('');
|
||||
const canManageStorage = ref<boolean>(false);
|
||||
|
||||
// 返回题库管理页面
|
||||
const goToQuestionBank = () => {
|
||||
@ -216,6 +222,9 @@ interface Question {
|
||||
type: string;
|
||||
category: string;
|
||||
difficulty: string;
|
||||
degree: string; // 程度:了解、熟悉、掌握
|
||||
ability: string; // 能力:识记、理解、应用
|
||||
status: string; // 状态:正常入库、未入库
|
||||
score: number;
|
||||
creator: string;
|
||||
createTime: string;
|
||||
@ -307,17 +316,36 @@ const createColumns = ({
|
||||
width: 80,
|
||||
align: 'center' as const
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center' as const,
|
||||
render(row: Question) {
|
||||
const isNormal = row.status === '正常入库';
|
||||
return h(NTag, {
|
||||
type: isNormal ? 'success' : 'warning',
|
||||
size: 'small'
|
||||
}, { default: () => row.status });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '试题内容',
|
||||
key: 'title',
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
},
|
||||
render(row: Question) {
|
||||
// 如果是子题目,添加缩进显示
|
||||
const prefix = row.parentId ? ' └ ' : '';
|
||||
return prefix + row.title;
|
||||
const content = prefix + row.title;
|
||||
// 限制显示长度,超过30个字符显示省略号
|
||||
const maxLength = 30;
|
||||
const displayContent = content.length > maxLength
|
||||
? content.substring(0, maxLength) + '...'
|
||||
: content;
|
||||
return displayContent;
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -363,6 +391,36 @@ const createColumns = ({
|
||||
return h(NTag, { type: diffInfo.type, size: 'small' }, { default: () => diffInfo.text });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '程度',
|
||||
key: 'degree',
|
||||
width: 80,
|
||||
align: 'center' as const,
|
||||
render(row: Question) {
|
||||
const degreeMap: { [key: string]: { text: string; type: any } } = {
|
||||
'了解': { text: '了解', type: 'info' },
|
||||
'熟悉': { text: '熟悉', type: 'warning' },
|
||||
'掌握': { text: '掌握', type: 'success' }
|
||||
};
|
||||
const degreeInfo = degreeMap[row.degree] || { text: row.degree, type: 'default' };
|
||||
return h(NTag, { type: degreeInfo.type, size: 'small' }, { default: () => degreeInfo.text });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '能力',
|
||||
key: 'ability',
|
||||
width: 80,
|
||||
align: 'center' as const,
|
||||
render(row: Question) {
|
||||
const abilityMap: { [key: string]: { text: string; type: any } } = {
|
||||
'识记': { text: '识记', type: 'info' },
|
||||
'理解': { text: '理解', type: 'warning' },
|
||||
'应用': { text: '应用', type: 'success' }
|
||||
};
|
||||
const abilityInfo = abilityMap[row.ability] || { text: row.ability, type: 'default' };
|
||||
return h(NTag, { type: abilityInfo.type, size: 'small' }, { default: () => abilityInfo.text });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '分值',
|
||||
key: 'score',
|
||||
@ -384,32 +442,32 @@ const createColumns = ({
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 120,
|
||||
width: 140,
|
||||
align: 'center' as const,
|
||||
render(row: Question) {
|
||||
const buttons: VNode[] = [];
|
||||
// 如果是已入库状态(正常入库),不显示编辑删除按钮
|
||||
const isStored = row.status === '正常入库';
|
||||
|
||||
buttons.push(
|
||||
if (isStored) {
|
||||
return h('span', { style: 'color: #999; font-size: 12px;' }, '已入库');
|
||||
}
|
||||
|
||||
return h(NSpace, { size: 'small' }, {
|
||||
default: () => [
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
ghost: true,
|
||||
style: 'margin: 0 3px;',
|
||||
onClick: () => handleAction('编辑', row)
|
||||
}, { default: () => '编辑' })
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
}, { default: () => '编辑' }),
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
ghost: true,
|
||||
style: 'margin: 0 3px;',
|
||||
onClick: () => handleAction('删除', row)
|
||||
}, { default: () => '删除' })
|
||||
);
|
||||
|
||||
return h(NSpace, {}, { default: () => buttons });
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -468,6 +526,36 @@ const getDifficultyText = (difficulty: number): string => {
|
||||
return difficultyMap[difficulty] || '未知';
|
||||
};
|
||||
|
||||
// 程度转换函数
|
||||
const getDegreeText = (degree: number): string => {
|
||||
const degreeMap: { [key: number]: string } = {
|
||||
0: '了解',
|
||||
1: '熟悉',
|
||||
2: '掌握'
|
||||
};
|
||||
return degreeMap[degree] || '未知';
|
||||
};
|
||||
|
||||
// 能力转换函数
|
||||
const getAbilityText = (ability: number): string => {
|
||||
const abilityMap: { [key: number]: string } = {
|
||||
0: '识记',
|
||||
1: '理解',
|
||||
2: '应用'
|
||||
};
|
||||
return abilityMap[ability] || '未知';
|
||||
};
|
||||
|
||||
// 状态转换函数
|
||||
const getStatusText = (status: number | null): string => {
|
||||
if (status === null) return '正常入库'; // 默认为正常入库
|
||||
const statusMap: { [key: number]: string } = {
|
||||
0: '正常入库',
|
||||
1: '未入库'
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
// 加载题目列表
|
||||
const loadQuestions = async () => {
|
||||
loading.value = true;
|
||||
@ -498,6 +586,9 @@ const loadQuestions = async () => {
|
||||
type: getQuestionTypeText(item.type),
|
||||
category: item.category || '未分类',
|
||||
difficulty: getDifficultyText(item.difficulty),
|
||||
degree: getDegreeText(item.degree ?? 1), // 程度:0=了解,1=熟悉,2=掌握
|
||||
ability: getAbilityText(item.ability ?? 1), // 能力:0=识记,1=理解,2=应用
|
||||
status: getStatusText(item.status), // 状态:0=正常入库,1=未入库
|
||||
score: item.score || 10,
|
||||
creator: item.createBy || '未知',
|
||||
createTime: item.createTime || new Date().toISOString(),
|
||||
@ -679,6 +770,35 @@ const deleteSelected = () => {
|
||||
console.log('批量删除:', selectedRowKeys.value);
|
||||
};
|
||||
|
||||
// 入出库管理
|
||||
const handleStorageManagement = async () => {
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
message.warning('请先选择要操作的题目');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔍 选中的题目ID列表:', selectedRowKeys.value);
|
||||
|
||||
try {
|
||||
// 调用入出库API
|
||||
const response = await ExamApi.whetherPutStorage(selectedRowKeys.value);
|
||||
console.log('📊 入出库API响应:', response);
|
||||
|
||||
if (response.data && (response.data.success || response.data.code === 200)) {
|
||||
message.success('入出库操作成功');
|
||||
// 重新加载题目列表
|
||||
await loadQuestions();
|
||||
// 清空选中状态
|
||||
selectedRowKeys.value = [];
|
||||
} else {
|
||||
message.error('入出库操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 入出库操作失败:', error);
|
||||
message.error('入出库操作失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
const editQuestion = async (id: string) => {
|
||||
console.log('🔍 编辑题目,题目ID:', id);
|
||||
|
||||
@ -785,12 +905,19 @@ const editQuestion = async (id: string) => {
|
||||
|
||||
console.log('🔍 完整题目数据:', completeQuestionData);
|
||||
|
||||
// 跳转到编辑页面,并传递完整的题目数据
|
||||
// 保存当前页面的查询参数,用于返回时恢复
|
||||
const currentQuery = new URLSearchParams(window.location.search).toString();
|
||||
if (currentQuery) {
|
||||
sessionStorage.setItem('questionManagementQuery', currentQuery);
|
||||
}
|
||||
|
||||
// 跳转到编辑页面,只传递必要的参数
|
||||
router.push({
|
||||
path: `/teacher/exam-management/add-question/${currentBankId.value}/${id}`,
|
||||
query: {
|
||||
questionData: JSON.stringify(completeQuestionData),
|
||||
mode: 'edit'
|
||||
mode: 'edit',
|
||||
title: currentBankTitle.value,
|
||||
createBy: currentBankCreateBy.value
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -853,8 +980,27 @@ const deleteQuestion = async (id: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前用户信息
|
||||
const getCurrentUser = async () => {
|
||||
try {
|
||||
const userInfo = await AuthApi.getUserInfo();
|
||||
if (userInfo.success && userInfo.result) {
|
||||
currentUser.value = userInfo.result.baseInfo.username;
|
||||
console.log('🔍 当前用户username:', currentUser.value);
|
||||
console.log('🔍 题库创建者createBy:', currentBankCreateBy.value);
|
||||
|
||||
// 检查是否有入出库管理权限
|
||||
canManageStorage.value = currentUser.value === currentBankCreateBy.value;
|
||||
console.log('🔍 是否有入出库权限:', canManageStorage.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 获取当前用户信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await getCurrentUser();
|
||||
loadCurrentBankInfo();
|
||||
loadQuestions();
|
||||
});
|
||||
@ -1063,6 +1209,32 @@ const deleteCategory = (categoryValue: string) => {
|
||||
|
||||
.question-table {
|
||||
margin-top: 20px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 表格高度自适应 */
|
||||
.question-table :deep(.n-data-table-wrapper) {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 表格行高优化 */
|
||||
.question-table :deep(.n-data-table-td) {
|
||||
padding: 8px 12px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 试题内容列样式 */
|
||||
.question-table :deep(.n-data-table-td:nth-child(4)) {
|
||||
max-width: 200px;
|
||||
word-break: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 操作按钮样式优化 */
|
||||
.question-table :deep(.n-space) {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 分类设置弹窗样式 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user