feat:练习讨论

This commit is contained in:
小张 2025-09-23 12:22:00 +08:00
parent 9aa5fbcea0
commit 5519a849a3
5 changed files with 276 additions and 56 deletions

View File

@ -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

View File

@ -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

View File

@ -403,15 +403,57 @@ const handleTypeChange = (type: string) => {
const goBack = () => {
//
const currentRoute = route.path;
const bankId = route.params.bankId;
// :idID
const bankId = route.params.id;
console.log('🔍 返回操作 - 当前路由:', currentRoute);
console.log('🔍 返回操作 - bankId:', bankId);
console.log('🔍 返回操作 - 所有路由参数:', route.params);
// bankIdundefined
let finalBankId = bankId;
if (!finalBankId) {
// URL
const pathMatch = currentRoute.match(/add-question\/([^\/]+)/);
if (pathMatch) {
finalBankId = pathMatch[1];
console.log('🔍 从路径提取到bankId:', finalBankId);
}
}
// referrersessionStorage
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 :idID
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);
}
}
});
//

View File

@ -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}`);
}
};

View File

@ -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;
}
/* 分类设置弹窗样式 */