feat: 对接部分题库接口;添加考试界面;部分界面样式优化
This commit is contained in:
parent
57eb57d0ce
commit
590af0951f
@ -2,6 +2,7 @@
|
|||||||
import { ApiRequest } from '../request'
|
import { ApiRequest } from '../request'
|
||||||
import type {
|
import type {
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
|
ApiResponseWithResult,
|
||||||
Repo,
|
Repo,
|
||||||
Question,
|
Question,
|
||||||
CreateRepoRequest,
|
CreateRepoRequest,
|
||||||
@ -36,19 +37,54 @@ export class ExamApi {
|
|||||||
/**
|
/**
|
||||||
* 获取课程题库
|
* 获取课程题库
|
||||||
*/
|
*/
|
||||||
static async getCourseRepoList(): Promise<ApiResponse<Repo[]>> {
|
static async getCourseRepoList(): Promise<ApiResponseWithResult<Repo[]>> {
|
||||||
const response = await ApiRequest.get<Repo[]>(`/biz/repo/repoList`)
|
const response = await ApiRequest.get<{ result: Repo[] }>(`/biz/repo/repoList`)
|
||||||
console.log('✅ 获取课程题库列表成功:', response)
|
console.log('✅ 获取课程题库列表成功:', response)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程列表(用于题库筛选)
|
||||||
|
*/
|
||||||
|
static async getCourseList(): Promise<ApiResponse<{ id: string; name: string }[]>> {
|
||||||
|
try {
|
||||||
|
// 调用现有的课程列表API,但只返回id和name字段
|
||||||
|
const response = await ApiRequest.get<any>('/biz/course/list')
|
||||||
|
console.log('✅ 获取课程列表成功:', response)
|
||||||
|
|
||||||
|
// 处理响应数据,只提取id和name
|
||||||
|
if (response.data && response.data.success && response.data.result) {
|
||||||
|
const courseList = response.data.result.map((course: any) => ({
|
||||||
|
id: course.id,
|
||||||
|
name: course.name || course.title || '未命名课程'
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: response.data.code || 200,
|
||||||
|
message: response.data.message || 'success',
|
||||||
|
data: courseList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果响应格式不符合预期,返回空数组
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取课程列表失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除题库
|
* 删除题库
|
||||||
*/
|
*/
|
||||||
static async deleteRepo(id: string): Promise<ApiResponse<string>> {
|
static async deleteRepo(id: string): Promise<ApiResponse<string>> {
|
||||||
console.log('🚀 删除题库:', { id })
|
console.log('🚀 删除题库:', { id })
|
||||||
const response = await ApiRequest.delete<string>('/gen/repo/repo/delete', {
|
const response = await ApiRequest.delete<string>('/gen/repo/repo/delete', {
|
||||||
params: { id }
|
id
|
||||||
})
|
})
|
||||||
console.log('✅ 删除题库成功:', response)
|
console.log('✅ 删除题库成功:', response)
|
||||||
return response
|
return response
|
||||||
|
@ -8,6 +8,16 @@ export interface ApiResponse<T = any> {
|
|||||||
timestamp?: string
|
timestamp?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 带有 result 包装的响应类型
|
||||||
|
export interface ApiResponseWithResult<T = any> {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: {
|
||||||
|
result: T
|
||||||
|
}
|
||||||
|
timestamp?: string
|
||||||
|
}
|
||||||
|
|
||||||
// 分页响应类型
|
// 分页响应类型
|
||||||
export interface PaginationResponse<T> {
|
export interface PaginationResponse<T> {
|
||||||
list: T[]
|
list: T[]
|
||||||
@ -695,6 +705,8 @@ export interface Repo {
|
|||||||
createTime: string
|
createTime: string
|
||||||
updateBy: string
|
updateBy: string
|
||||||
updateTime: string
|
updateTime: string
|
||||||
|
courseId?: string // 所属课程ID
|
||||||
|
courseName?: string // 所属课程名称
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Question {
|
export interface Question {
|
||||||
|
301
src/data/mockExamData.ts
Normal file
301
src/data/mockExamData.ts
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
// 模拟考试数据
|
||||||
|
export const mockExamData = {
|
||||||
|
id: "exam_001",
|
||||||
|
examName: "计算机基础知识测试",
|
||||||
|
duration: 90, // 考试时长(分钟)
|
||||||
|
totalScore: 100,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "section_1",
|
||||||
|
title: "第一大题:单选题",
|
||||||
|
type: "section",
|
||||||
|
description: "请从四个选项中选择一个正确答案",
|
||||||
|
subQuestions: [
|
||||||
|
{
|
||||||
|
id: "q1",
|
||||||
|
type: "single_choice",
|
||||||
|
title: "在HTML中,用于定义无序列表的标签是?",
|
||||||
|
score: 2,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q1_a",
|
||||||
|
content: "<ol>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q1_b",
|
||||||
|
content: "<ul>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q1_c",
|
||||||
|
content: "<li>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q1_d",
|
||||||
|
content: "<dl>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q2",
|
||||||
|
type: "single_choice",
|
||||||
|
title: "CSS中用于设置字体大小的属性是?",
|
||||||
|
score: 2,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q2_a",
|
||||||
|
content: "font-weight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q2_b",
|
||||||
|
content: "font-size"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q2_c",
|
||||||
|
content: "font-family"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q2_d",
|
||||||
|
content: "font-style"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3",
|
||||||
|
type: "single_choice",
|
||||||
|
title: "JavaScript中声明变量使用的关键字是?",
|
||||||
|
score: 2,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q3_a",
|
||||||
|
content: "var"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3_b",
|
||||||
|
content: "let"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3_c",
|
||||||
|
content: "const"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3_d",
|
||||||
|
content: "以上都是"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q4",
|
||||||
|
type: "single_choice",
|
||||||
|
title: "HTTP协议默认端口号是?",
|
||||||
|
score: 2,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q4_a",
|
||||||
|
content: "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q4_b",
|
||||||
|
content: "80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q4_c",
|
||||||
|
content: "443"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q4_d",
|
||||||
|
content: "8080"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q5",
|
||||||
|
type: "single_choice",
|
||||||
|
title: "下列哪个不是Vue.js的生命周期钩子?",
|
||||||
|
score: 2,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q5_a",
|
||||||
|
content: "mounted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q5_b",
|
||||||
|
content: "created"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q5_c",
|
||||||
|
content: "destroyed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q5_d",
|
||||||
|
content: "rendered"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "section_2",
|
||||||
|
title: "第二大题:多选题",
|
||||||
|
type: "section",
|
||||||
|
description: "请从选项中选择所有正确答案",
|
||||||
|
subQuestions: [
|
||||||
|
{
|
||||||
|
id: "q6",
|
||||||
|
type: "multiple_choice",
|
||||||
|
title: "下列哪些是前端开发常用的框架?",
|
||||||
|
score: 3,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q6_a",
|
||||||
|
content: "React"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q6_b",
|
||||||
|
content: "Vue.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q6_c",
|
||||||
|
content: "Angular"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q6_d",
|
||||||
|
content: "Express.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q7",
|
||||||
|
type: "multiple_choice",
|
||||||
|
title: "CSS布局方式包括哪些?",
|
||||||
|
score: 3,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q7_a",
|
||||||
|
content: "Flexbox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q7_b",
|
||||||
|
content: "Grid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q7_c",
|
||||||
|
content: "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q7_d",
|
||||||
|
content: "Position"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q8",
|
||||||
|
type: "multiple_choice",
|
||||||
|
title: "HTTP状态码中,表示成功的有哪些?",
|
||||||
|
score: 3,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: "q8_a",
|
||||||
|
content: "200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q8_b",
|
||||||
|
content: "201"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q8_c",
|
||||||
|
content: "404"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q8_d",
|
||||||
|
content: "500"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "section_3",
|
||||||
|
title: "第三大题:判断题",
|
||||||
|
type: "section",
|
||||||
|
description: "请判断下列说法是否正确",
|
||||||
|
subQuestions: [
|
||||||
|
{
|
||||||
|
id: "q9",
|
||||||
|
type: "true_false",
|
||||||
|
title: "HTML是一种编程语言。",
|
||||||
|
score: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q10",
|
||||||
|
type: "true_false",
|
||||||
|
title: "CSS可以用来控制网页的布局和样式。",
|
||||||
|
score: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q11",
|
||||||
|
type: "true_false",
|
||||||
|
title: "JavaScript只能在浏览器中运行。",
|
||||||
|
score: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q12",
|
||||||
|
type: "true_false",
|
||||||
|
title: "Git是一个版本控制系统。",
|
||||||
|
score: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "section_4",
|
||||||
|
title: "第四大题:填空题",
|
||||||
|
type: "section",
|
||||||
|
description: "请在空白处填入正确答案",
|
||||||
|
subQuestions: [
|
||||||
|
{
|
||||||
|
id: "q13",
|
||||||
|
type: "fill_blank",
|
||||||
|
title: "HTML文档的基本结构包括_____、_____和_____三个主要部分。",
|
||||||
|
score: 3,
|
||||||
|
fillBlanks: [
|
||||||
|
{ id: "blank1", answer: "DOCTYPE声明" },
|
||||||
|
{ id: "blank2", answer: "head" },
|
||||||
|
{ id: "blank3", answer: "body" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q14",
|
||||||
|
type: "fill_blank",
|
||||||
|
title: "CSS选择器中,_____选择器用于选择具有特定ID的元素,_____选择器用于选择具有特定类名的元素。",
|
||||||
|
score: 2,
|
||||||
|
fillBlanks: [
|
||||||
|
{ id: "blank4", answer: "ID" },
|
||||||
|
{ id: "blank5", answer: "类" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "section_5",
|
||||||
|
title: "第五大题:简答题",
|
||||||
|
type: "section",
|
||||||
|
description: "请根据题目要求作答",
|
||||||
|
subQuestions: [
|
||||||
|
{
|
||||||
|
id: "q15",
|
||||||
|
type: "short_answer",
|
||||||
|
title: "请简述响应式网页设计的基本原理和实现方法。",
|
||||||
|
score: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q16",
|
||||||
|
type: "short_answer",
|
||||||
|
title: "解释JavaScript中异步编程的概念,并举例说明。",
|
||||||
|
score: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出考试数据用于在组件中使用
|
||||||
|
export default mockExamData;
|
@ -72,6 +72,7 @@ import AddQuestion from '@/views/teacher/ExamPages/AddQuestion.vue'
|
|||||||
import StudentList from '@/views/teacher/ExamPages/StudentList.vue'
|
import StudentList from '@/views/teacher/ExamPages/StudentList.vue'
|
||||||
import GradingPage from '@/views/teacher/ExamPages/GradingPage.vue'
|
import GradingPage from '@/views/teacher/ExamPages/GradingPage.vue'
|
||||||
import ExamTaking from '@/views/teacher/ExamPages/ExamTaking.vue'
|
import ExamTaking from '@/views/teacher/ExamPages/ExamTaking.vue'
|
||||||
|
import ExamNoticeBeforeStart from '@/views/teacher/ExamPages/ExamNoticeBeforeStart.vue'
|
||||||
|
|
||||||
import ChapterEditor from '@/views/teacher/course/ChapterEditor.vue'
|
import ChapterEditor from '@/views/teacher/course/ChapterEditor.vue'
|
||||||
|
|
||||||
@ -366,6 +367,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/exam/notice/:id',
|
||||||
|
name: 'ExamNoticeBeforeStart',
|
||||||
|
component: ExamNoticeBeforeStart,
|
||||||
|
meta: { title: '考前须知' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/taking/:id',
|
path: '/taking/:id',
|
||||||
name: 'ExamTaking',
|
name: 'ExamTaking',
|
||||||
@ -457,7 +464,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: { title: '学习中心', requiresAuth: true }
|
meta: { title: '学习中心', requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: '/profile/:tabKey?',
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
component: Profile,
|
component: Profile,
|
||||||
meta: { title: '个人中心', requiresAuth: true }
|
meta: { title: '个人中心', requiresAuth: true }
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 考试说明页面 -->
|
<!-- 考试说明页面 -->
|
||||||
<div class="exam-instructions" v-if="!examStarted">
|
<div class="exam-instructions" v-if="!examStarted">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -881,8 +879,15 @@ const questionsByType = computed(() => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 开始考试
|
// 开始考试(从考前须知页面跳转到这里)
|
||||||
const startExam = () => {
|
const startExam = () => {
|
||||||
|
// 跳转到考前须知页面
|
||||||
|
const examId = sectionId.value || 1 // 使用 sectionId 作为考试ID,如果没有则默认为1
|
||||||
|
router.push(`/exam/notice/${examId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际开始考试
|
||||||
|
const beginExam = () => {
|
||||||
examStarted.value = true
|
examStarted.value = true
|
||||||
remainingTime.value = examDuration.value * 60
|
remainingTime.value = examDuration.value * 60
|
||||||
startTimer()
|
startTimer()
|
||||||
@ -1220,7 +1225,7 @@ onMounted(() => {
|
|||||||
// 检查是否从考前须知页面跳转过来,如果是则直接开始考试
|
// 检查是否从考前须知页面跳转过来,如果是则直接开始考试
|
||||||
const fromNotice = route.query.fromNotice
|
const fromNotice = route.query.fromNotice
|
||||||
if (fromNotice === 'true') {
|
if (fromNotice === 'true') {
|
||||||
startExam()
|
beginExam()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1071,14 +1071,14 @@
|
|||||||
import { ref, computed, onMounted, onActivated, reactive } from 'vue'
|
import { ref, computed, onMounted, onActivated, reactive } from 'vue'
|
||||||
import { useMessage, NInput, NForm, NFormItem } from 'naive-ui'
|
import { useMessage, NInput, NForm, NFormItem } from 'naive-ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
||||||
import QuillEditor from '@/components/common/QuillEditor.vue'
|
import QuillEditor from '@/components/common/QuillEditor.vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
// 轮播图根据语言动态切换
|
// 轮播图根据语言动态切换
|
||||||
const bannerImage = computed(() => {
|
const bannerImage = computed(() => {
|
||||||
return locale.value === 'zh' ? '/banners/banner8.png' : '/banners/banner1-en.png'
|
return locale.value === 'zh' ? '/banners/banner8.png' : '/banners/banner1-en.png'
|
||||||
@ -2288,6 +2288,7 @@ const isMessageTab = computed(() => activeTab.value === 'message')
|
|||||||
// 处理菜单选择
|
// 处理菜单选择
|
||||||
const handleMenuSelect = (key: TabType) => {
|
const handleMenuSelect = (key: TabType) => {
|
||||||
activeTab.value = key
|
activeTab.value = key
|
||||||
|
router.push(`/profile/${key}`)
|
||||||
// message.info(`切换到${getTabTitle(key)}`)
|
// message.info(`切换到${getTabTitle(key)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2392,7 +2393,8 @@ const startExam = (examId: number) => {
|
|||||||
|
|
||||||
// 继续考试
|
// 继续考试
|
||||||
const continueExam = (examId: number) => {
|
const continueExam = (examId: number) => {
|
||||||
message.info(`继续考试 ${examId}`)
|
// message.info(`继续考试 ${examId}`)
|
||||||
|
router.push(`/exam/notice/${examId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看考试结果
|
// 查看考试结果
|
||||||
@ -2759,6 +2761,9 @@ onMounted(() => {
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabKey = <TabType>route.params.tabKey || 'courses'
|
||||||
|
handleMenuSelect(tabKey)
|
||||||
})
|
})
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
333
src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue
Normal file
333
src/views/teacher/ExamPages/ExamNoticeBeforeStart.vue
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
<template>
|
||||||
|
<div class="exam-notice-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h1 class="title">考试中心</h1>
|
||||||
|
<span class="subTit">涵盖多种题型,全方位考核,AI智能阅卷</span>
|
||||||
|
</div>
|
||||||
|
<div class="exam-notice-content">
|
||||||
|
<!-- 考前须知主体 -->
|
||||||
|
<div class="notice-main">
|
||||||
|
<h1 class="notice-title">考前须知</h1>
|
||||||
|
|
||||||
|
<!-- 考试信息 -->
|
||||||
|
<div class="exam-info">
|
||||||
|
<div class="exam-meta">
|
||||||
|
<span class="exam-time">考试时间:2025年8月1日-8月16日</span>
|
||||||
|
<span class="exam-name">考试名称:[2025] 教学技能提高暨自主学习课程期末考试</span>
|
||||||
|
<span class="exam-duration">考试时长:120分钟</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 考试规则列表 -->
|
||||||
|
<div class="rules-list">
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">1.</span>
|
||||||
|
<span class="rule-text">考试时间:2025年8月1日-8月16日,在此期间考生可自行安排开始考试,考试时长共120分钟。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">2.</span>
|
||||||
|
<span class="rule-text">考生必须持本人身份证明准证入场,两证缺一者不得参加考试。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">3.</span>
|
||||||
|
<span class="rule-text">考试时严禁考生携带相关文书籍、资料、笔记本电脑、自带纸张等。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">4.</span>
|
||||||
|
<span
|
||||||
|
class="rule-text">本次考试采用机读卡答题方式,考生在机读卡上填写姓名,用2B铅笔在规定位置填涂考号、答案。未按规定在机读卡上作答造成本次考试无效者记录的,责任自负。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">5.</span>
|
||||||
|
<span class="rule-text">考生若需要借用考试用具,须举手示意监考人员呼叫,不得向其他考生直接借取。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">6.</span>
|
||||||
|
<span
|
||||||
|
class="rule-text">考试时,请考生自觉关闭手机,并将携带的随身证明件、签字笔、2B铅笔、橡皮除以外的物品存放在指定位置,若手机响过在考生座位上响起按作弊处理。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">7.</span>
|
||||||
|
<span class="rule-text">考生答题完毕不得诵背清。机读卡带离考场,否则现场作弊处理。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">8.</span>
|
||||||
|
<span class="rule-text">找人代考者,按作弊处理,直至替考者离开考场,假证件予以没收。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">9.</span>
|
||||||
|
<span class="rule-text">必须服从监考人员的管理,严格遵守考场纪律,对不服从监考人员管教,抗拒者按作弊的,取消考试资格。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">10.</span>
|
||||||
|
<span
|
||||||
|
class="rule-text">考生违反考生须知有关规定或或违反考场纪律进行警察时,监考人员可要求该名考生离开考场,收回试卷,,取消考试资格,并在《考场记录单》上作好记录。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-item">
|
||||||
|
<span class="rule-number">11.</span>
|
||||||
|
<span class="rule-text">考试成绩将于十个工作日完成此间进行公示,查询成绩网址:www.baidu.com。</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contact-info">
|
||||||
|
<span>*如有疑问,可致电:0871-5533221。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 按钮区域 -->
|
||||||
|
<div class="button-area">
|
||||||
|
<n-button size="large" :type="countdown > 0 ? 'default' : 'primary'" :disabled="countdown > 0"
|
||||||
|
@click="handleStartExam">
|
||||||
|
我已知晓,开始考试 <span v-if="countdown > 0">({{ countdown }})秒</span>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { NButton } from 'naive-ui'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const countdown = ref(10)
|
||||||
|
let timer: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
|
// 倒计时功能
|
||||||
|
onMounted(() => {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (countdown.value > 0) {
|
||||||
|
countdown.value--
|
||||||
|
} else {
|
||||||
|
// 倒计时结束,可以开始考试
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 开始考试
|
||||||
|
const handleStartExam = () => {
|
||||||
|
if (countdown.value > 0) {
|
||||||
|
return // 倒计时未结束不能开始
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置考试数据到 sessionStorage(模拟从API获取的数据)
|
||||||
|
const examData = {
|
||||||
|
examId: route.params.id,
|
||||||
|
examName: '[2025] 教学技能提高暨自主学习课程期末考试',
|
||||||
|
duration: 120, // 120分钟
|
||||||
|
questions: [] // 这里可以设置真实的考试题目数据
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem('examData', JSON.stringify(examData))
|
||||||
|
|
||||||
|
// 获取考试ID并跳转到考试页面
|
||||||
|
const examId = route.params.id
|
||||||
|
router.replace(`/taking/${examId}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.exam-notice-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-notice-content {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 10px auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
padding: 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-image: url('/banners/考前须知.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header>.title{
|
||||||
|
font-family: AlimamaShuHeiTi, AlimamaShuHeiTi;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header>.subtitle{
|
||||||
|
font-family: PingFangSC, PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-name {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-main {
|
||||||
|
padding: 40px 60px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-info {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-time,
|
||||||
|
.exam-name,
|
||||||
|
.exam-duration {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-name {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-list {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-number {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 24px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-text {
|
||||||
|
color: #555;
|
||||||
|
font-size: 14px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info {
|
||||||
|
margin-top: 30px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button,
|
||||||
|
.start-button {
|
||||||
|
padding: 12px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-button {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
border-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.notice-main {
|
||||||
|
padding: 30px 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-meta {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-name {
|
||||||
|
text-align: left;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-area {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-button,
|
||||||
|
.start-button {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
@ -6,56 +6,32 @@
|
|||||||
<n-button type="primary" @click="addQuestionBank">新建题库</n-button>
|
<n-button type="primary" @click="addQuestionBank">新建题库</n-button>
|
||||||
<n-button ghost @click="importQuestionBank">导入题库</n-button>
|
<n-button ghost @click="importQuestionBank">导入题库</n-button>
|
||||||
<n-button ghost @click="exportQuestionBank">导出题库</n-button>
|
<n-button ghost @click="exportQuestionBank">导出题库</n-button>
|
||||||
<n-button type="error" ghost @click="deleteSelected" :disabled="selectedRowKeys.length === 0">删除</n-button>
|
<n-button type="error" ghost @click="deleteSelected"
|
||||||
<n-input
|
:disabled="selectedRowKeys.length === 0">删除</n-button>
|
||||||
v-model:value="filters.keyword"
|
<n-select v-model:value="filters.courseId" placeholder="所属课程" :options="courseOptions" clearable
|
||||||
placeholder="请输入想要搜索的内容"
|
style="width: 150px" @update:value="searchQuestionBanks" />
|
||||||
style="width: 200px"
|
<n-input v-model:value="filters.keyword" placeholder="请输入想要搜索的内容" style="width: 200px" clearable />
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
<n-button type="primary" @click="searchQuestionBanks">搜索</n-button>
|
<n-button type="primary" @click="searchQuestionBanks">搜索</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-data-table
|
<n-data-table ref="tableRef" :columns="columns" :data="questionBankList" :loading="loading"
|
||||||
ref="tableRef"
|
:pagination="paginationConfig" :row-key="(row: QuestionBank) => row.id" :checked-row-keys="selectedRowKeys"
|
||||||
:columns="columns"
|
@update:checked-row-keys="handleCheck" class="question-bank-table" :single-line="false" />
|
||||||
:data="questionBankList"
|
|
||||||
:loading="loading"
|
|
||||||
:pagination="paginationConfig"
|
|
||||||
:row-key="(row: QuestionBank) => row.id"
|
|
||||||
:checked-row-keys="selectedRowKeys"
|
|
||||||
@update:checked-row-keys="handleCheck"
|
|
||||||
class="question-bank-table"
|
|
||||||
:single-line="false"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 新建/编辑题库弹窗 -->
|
<!-- 新建/编辑题库弹窗 -->
|
||||||
<n-modal
|
<n-modal v-model:show="showCreateModal" preset="dialog" :title="isEditMode ? '编辑题库' : '新建题库'"
|
||||||
v-model:show="showCreateModal"
|
style="width: 500px;">
|
||||||
preset="dialog"
|
|
||||||
:title="isEditMode ? '编辑题库' : '新建题库'"
|
|
||||||
style="width: 500px;"
|
|
||||||
>
|
|
||||||
<div class="create-modal-content">
|
<div class="create-modal-content">
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>题库名称:</label>
|
<label>题库名称:</label>
|
||||||
<n-input
|
<n-input v-model:value="createForm.name" placeholder="请输入题库名称" style="width: 100%;" />
|
||||||
v-model:value="createForm.name"
|
|
||||||
placeholder="请输入题库名称"
|
|
||||||
style="width: 100%;"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>题库描述:</label>
|
<label>题库描述:</label>
|
||||||
<n-input
|
<n-input v-model:value="createForm.description" type="textarea" placeholder="请输入题库描述"
|
||||||
v-model:value="createForm.description"
|
style="width: 100%;" :rows="3" />
|
||||||
type="textarea"
|
|
||||||
placeholder="请输入题库描述"
|
|
||||||
style="width: 100%;"
|
|
||||||
:rows="3"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -70,22 +46,18 @@
|
|||||||
</n-modal>
|
</n-modal>
|
||||||
|
|
||||||
<!-- 导入弹窗 -->
|
<!-- 导入弹窗 -->
|
||||||
<ImportModal
|
<ImportModal v-model:show="showImportModal" template-name="question_bank_template.xlsx"
|
||||||
v-model:show="showImportModal"
|
import-type="questionBank" @success="handleImportSuccess" @template-download="handleTemplateDownload" />
|
||||||
template-name="question_bank_template.xlsx"
|
|
||||||
import-type="questionBank"
|
|
||||||
@success="handleImportSuccess"
|
|
||||||
@template-download="handleTemplateDownload"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, h, VNode } from 'vue';
|
import { ref, reactive, computed, onMounted, h, VNode } from 'vue';
|
||||||
import { NButton, NSpace, useMessage, useDialog } from 'naive-ui';
|
import { NButton, NSpace, NSelect, useMessage, useDialog } from 'naive-ui';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import ImportModal from '@/components/common/ImportModal.vue';
|
import ImportModal from '@/components/common/ImportModal.vue';
|
||||||
import { ExamApi } from '@/api'
|
import { ExamApi } from '@/api';
|
||||||
|
import type { Repo } from '@/api/types';
|
||||||
|
|
||||||
// 消息提示和对话框
|
// 消息提示和对话框
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
@ -104,11 +76,19 @@ interface QuestionBank {
|
|||||||
creator: string;
|
creator: string;
|
||||||
createTime: string;
|
createTime: string;
|
||||||
lastModified: string;
|
lastModified: string;
|
||||||
|
courseName?: string; // 所属课程名称
|
||||||
|
}
|
||||||
|
|
||||||
|
// 课程选项接口
|
||||||
|
interface CourseOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 筛选条件
|
// 筛选条件
|
||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
keyword: ''
|
keyword: '',
|
||||||
|
courseId: '' // 所属课程筛选
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +97,9 @@ const loading = ref(false);
|
|||||||
const selectedRowKeys = ref<string[]>([]);
|
const selectedRowKeys = ref<string[]>([]);
|
||||||
const questionBankList = ref<QuestionBank[]>([]);
|
const questionBankList = ref<QuestionBank[]>([]);
|
||||||
|
|
||||||
|
// 课程列表状态
|
||||||
|
const courseOptions = ref<CourseOption[]>([]);
|
||||||
|
|
||||||
// 新建/编辑题库相关状态
|
// 新建/编辑题库相关状态
|
||||||
const showCreateModal = ref(false);
|
const showCreateModal = ref(false);
|
||||||
const isEditMode = ref(false);
|
const isEditMode = ref(false);
|
||||||
@ -178,7 +161,7 @@ const createColumns = ({
|
|||||||
{
|
{
|
||||||
title: '题库名称',
|
title: '题库名称',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 200,
|
width: 180,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
tooltip: true
|
tooltip: true
|
||||||
},
|
},
|
||||||
@ -196,7 +179,7 @@ const createColumns = ({
|
|||||||
{
|
{
|
||||||
title: '题库描述',
|
title: '题库描述',
|
||||||
key: 'description',
|
key: 'description',
|
||||||
width: 250,
|
width: 200,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
tooltip: true
|
tooltip: true
|
||||||
}
|
}
|
||||||
@ -204,12 +187,27 @@ const createColumns = ({
|
|||||||
{
|
{
|
||||||
title: '题目数量',
|
title: '题目数量',
|
||||||
key: 'questionCount',
|
key: 'questionCount',
|
||||||
width: 100,
|
width: 80,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属课程',
|
||||||
|
key: 'courseName',
|
||||||
|
width: 160,
|
||||||
|
align: 'center' as const,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
render(row: QuestionBank) {
|
render(row: QuestionBank) {
|
||||||
return h('span', { style: 'color: #1890ff; font-weight: 500;' }, row.questionCount.toString());
|
return row.courseName || '暂无课程';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '权限',
|
||||||
|
key: 'permissions',
|
||||||
|
width: 160,
|
||||||
|
align: 'center' as const
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '创建人',
|
title: '创建人',
|
||||||
key: 'creator',
|
key: 'creator',
|
||||||
@ -219,19 +217,13 @@ const createColumns = ({
|
|||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'createTime',
|
key: 'createTime',
|
||||||
width: 160,
|
width: 150,
|
||||||
align: 'center' as const
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '最后修改',
|
|
||||||
key: 'lastModified',
|
|
||||||
width: 160,
|
|
||||||
align: 'center' as const
|
align: 'center' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 150,
|
width: 180,
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render(row: QuestionBank) {
|
render(row: QuestionBank) {
|
||||||
const buttons: VNode[] = [];
|
const buttons: VNode[] = [];
|
||||||
@ -241,7 +233,6 @@ const createColumns = ({
|
|||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
ghost: true,
|
ghost: true,
|
||||||
style: 'margin: 0 3px;',
|
|
||||||
onClick: () => handleAction('进入', row)
|
onClick: () => handleAction('进入', row)
|
||||||
}, { default: () => '进入' })
|
}, { default: () => '进入' })
|
||||||
);
|
);
|
||||||
@ -250,7 +241,6 @@ const createColumns = ({
|
|||||||
h(NButton, {
|
h(NButton, {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
ghost: true,
|
ghost: true,
|
||||||
style: 'margin: 0 3px;',
|
|
||||||
onClick: () => handleAction('编辑', row)
|
onClick: () => handleAction('编辑', row)
|
||||||
}, { default: () => '编辑' })
|
}, { default: () => '编辑' })
|
||||||
);
|
);
|
||||||
@ -260,7 +250,6 @@ const createColumns = ({
|
|||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
ghost: true,
|
ghost: true,
|
||||||
style: 'margin: 0 3px;',
|
|
||||||
onClick: () => handleAction('删除', row)
|
onClick: () => handleAction('删除', row)
|
||||||
}, { default: () => '删除' })
|
}, { default: () => '删除' })
|
||||||
);
|
);
|
||||||
@ -275,7 +264,7 @@ const createColumns = ({
|
|||||||
const columns = createColumns({
|
const columns = createColumns({
|
||||||
handleAction: (action, row) => {
|
handleAction: (action, row) => {
|
||||||
if (action === '进入') {
|
if (action === '进入') {
|
||||||
enterQuestionBank(row.id);
|
enterQuestionBank(row.id, row.name);
|
||||||
} else if (action === '编辑') {
|
} else if (action === '编辑') {
|
||||||
editQuestionBank(row.id);
|
editQuestionBank(row.id);
|
||||||
} else if (action === '删除') {
|
} else if (action === '删除') {
|
||||||
@ -284,27 +273,6 @@ const columns = createColumns({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成模拟数据
|
|
||||||
const generateMockData = (): QuestionBank[] => {
|
|
||||||
const mockData: QuestionBank[] = [];
|
|
||||||
const creators = ['王建国', '李明', '张三', '刘老师', '陈教授'];
|
|
||||||
const names = ['计算机基础题库', '数学专业题库', '英语考试题库', '物理练习题库', '化学综合题库'];
|
|
||||||
|
|
||||||
for (let i = 1; i <= 20; i++) {
|
|
||||||
mockData.push({
|
|
||||||
id: '1960998116632399873',
|
|
||||||
sequence: i,
|
|
||||||
name: names[Math.floor(Math.random() * names.length)] + ` ${i}`,
|
|
||||||
description: `这是一个包含多种题型的综合性题库,适用于教学和考试场景...`,
|
|
||||||
questionCount: Math.floor(Math.random() * 500) + 50,
|
|
||||||
creator: creators[Math.floor(Math.random() * creators.length)],
|
|
||||||
createTime: '2025.08.20 09:20',
|
|
||||||
lastModified: '2025.08.28 15:30'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return mockData;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理复选框选择
|
// 处理复选框选择
|
||||||
const handleCheck = (rowKeys: string[]) => {
|
const handleCheck = (rowKeys: string[]) => {
|
||||||
selectedRowKeys.value = rowKeys;
|
selectedRowKeys.value = rowKeys;
|
||||||
@ -316,17 +284,55 @@ const searchQuestionBanks = () => {
|
|||||||
loadQuestionBanks();
|
loadQuestionBanks();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据课程ID获取课程名称
|
||||||
|
const getCourseNameById = (courseId: string): string => {
|
||||||
|
const course = courseOptions.value.find(option => option.value === courseId);
|
||||||
|
return course ? course.label : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载课程列表
|
||||||
|
const loadCourseList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await ExamApi.getCourseList();
|
||||||
|
console.log('✅ 获取课程列表:', res.data);
|
||||||
|
|
||||||
|
courseOptions.value = [
|
||||||
|
{ label: '全部课程', value: '' },
|
||||||
|
...res.data.map(course => ({
|
||||||
|
label: course.name,
|
||||||
|
value: course.id
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课程列表失败:', error);
|
||||||
|
// 如果加载失败,至少提供一个默认选项
|
||||||
|
courseOptions.value = [{ label: '全部课程', value: '' }];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 加载题库列表
|
// 加载题库列表
|
||||||
const loadQuestionBanks = async () => {
|
const loadQuestionBanks = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
// TODO API调用
|
// API调用获取题库列表
|
||||||
// await ExamApi.getCourseRepoList();
|
const res = await ExamApi.getCourseRepoList();
|
||||||
|
console.log('✅ 获取题库列表:', res.data.result);
|
||||||
|
|
||||||
const allData = generateMockData();
|
// 将API返回的Repo[]类型映射为QuestionBank[]类型
|
||||||
// 模拟筛选
|
const apiData: QuestionBank[] = res.data.result.map((repo: Repo, index: number) => ({
|
||||||
let filteredData = allData;
|
id: repo.id,
|
||||||
|
sequence: index + 1,
|
||||||
|
name: repo.title,
|
||||||
|
description: repo.remark || '暂无描述',
|
||||||
|
questionCount: repo.questionCount || 0,
|
||||||
|
creator: repo.createBy || '未知',
|
||||||
|
createTime: repo.createTime || '',
|
||||||
|
lastModified: repo.updateTime || '',
|
||||||
|
courseName: repo.courseName || '暂无课程'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 应用搜索筛选
|
||||||
|
let filteredData = apiData;
|
||||||
if (filters.keyword) {
|
if (filters.keyword) {
|
||||||
filteredData = filteredData.filter(item =>
|
filteredData = filteredData.filter(item =>
|
||||||
item.name.includes(filters.keyword) ||
|
item.name.includes(filters.keyword) ||
|
||||||
@ -335,6 +341,14 @@ const loadQuestionBanks = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用课程筛选
|
||||||
|
if (filters.courseId) {
|
||||||
|
const courseName = getCourseNameById(filters.courseId);
|
||||||
|
filteredData = filteredData.filter(item =>
|
||||||
|
item.courseName === courseName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 分页处理
|
// 分页处理
|
||||||
pagination.total = filteredData.length;
|
pagination.total = filteredData.length;
|
||||||
const start = (pagination.page - 1) * pagination.pageSize;
|
const start = (pagination.page - 1) * pagination.pageSize;
|
||||||
@ -420,8 +434,8 @@ const deleteSelected = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 进入题库(跳转到试题管理页面)
|
// 进入题库(跳转到试题管理页面)
|
||||||
const enterQuestionBank = (bankId: string) => {
|
const enterQuestionBank = (bankId: string, bankTitle: string) => {
|
||||||
router.push(`/teacher/exam-management/question-bank/${bankId}/questions`);
|
router.push(`/teacher/exam-management/question-bank/${bankId}/questions?title=${bankTitle}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const editQuestionBank = (id: string) => {
|
const editQuestionBank = (id: string) => {
|
||||||
@ -541,6 +555,7 @@ const handleTemplateDownload = (type?: string) => {
|
|||||||
|
|
||||||
// 组件挂载时加载数据
|
// 组件挂载时加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
loadCourseList();
|
||||||
loadQuestionBanks();
|
loadQuestionBanks();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
<n-icon><ChevronBackOutline /></n-icon>
|
<n-icon><ChevronBackOutline /></n-icon>
|
||||||
题库管理
|
题库管理
|
||||||
</n-breadcrumb-item>
|
</n-breadcrumb-item>
|
||||||
<n-breadcrumb-item>{{ currentBankName }}</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">{{ currentBankName }} - 试题管理</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>
|
||||||
@ -190,6 +190,8 @@ const route = useRoute();
|
|||||||
|
|
||||||
// 当前题库信息
|
// 当前题库信息
|
||||||
const currentBankId = computed(() => route.params.bankId as string);
|
const currentBankId = computed(() => route.params.bankId as string);
|
||||||
|
const currentBankTitle = computed(() => route.query.title as string);
|
||||||
|
|
||||||
const currentBankName = ref('加载中...');
|
const currentBankName = ref('加载中...');
|
||||||
|
|
||||||
// 返回题库管理页面
|
// 返回题库管理页面
|
||||||
|
Loading…
x
Reference in New Issue
Block a user