2025-09-11 14:34:30 +08:00

524 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="marking-center">
<!-- Tab切换 -->
<div class="tab-container">
<n-tabs
v-model:value="activeTab"
type="line"
animated
@update:value="handleTabChange"
>
<n-tab-pane name="all" tab="全部">
</n-tab-pane>
<n-tab-pane name="not-started" tab="未开始">
</n-tab-pane>
<n-tab-pane name="in-progress" tab="进行中">
</n-tab-pane>
<n-tab-pane name="completed" tab="已结束">
</n-tab-pane>
</n-tabs>
</div>
<!-- 筛选栏 -->
<div class="filter-container">
<div class="filter-right">
<n-select
v-model:value="examFilter"
:options="examFilterOptions"
placeholder="考试"
style="width: 120px; margin-right: 16px;"
/>
<n-select
v-model:value="gradeFilter"
:options="gradeFilterOptions"
placeholder="班级名称"
style="width: 120px;"
/>
</div>
</div>
<!-- 试卷列表 -->
<div class="exam-list">
<div
v-for="exam in filteredExams"
:key="exam.id"
class="exam-item"
:class="{ 'completed': exam.status === 'completed' }"
>
<div class="exam-content">
<div class="exam-header">
<n-tag
:type="getStatusType(exam.status)"
:bordered="false"
size="small"
class="status-tag"
>
{{ getStatusText(exam.status) }}
</n-tag>
<span class="exam-title">{{ exam.title }}</span>
</div>
<div class="exam-description">
{{ exam.description }}
</div>
<div class="exam-meta">
<div class="meta-item">
<n-icon :component="PersonOutline" />
发布人
<span>{{ exam.creator }}</span>
</div>
<div class="meta-item">
<n-icon :component="CalendarOutline" />
<span>{{ exam.duration }}</span>
</div>
</div>
<div class="exam-actions">
<n-button
text
type="primary"
@click="handleViewDetails(exam)"
>
试卷设置
</n-button>
<n-button
text
type="primary"
@click="handleDelete(exam)"
>
删除
</n-button>
</div>
</div>
<div class="exam-stats">
<div class="stats-item">
<div class="stats-number">{{ exam.totalQuestions }}</div>
<div class="stats-label">试题</div>
</div>
<div class="stats-item">
<div class="stats-number">{{ exam.submittedCount }}</div>
<div class="stats-label">已交</div>
</div>
<div class="stats-item">
<div class="stats-number">{{ exam.gradedCount }}</div>
<div class="stats-label">{{ exam.status === 'in-progress' ? '0未交' : '0未交' }}</div>
</div>
</div>
<div class="exam-action-button">
<n-button
:type="exam.status === 'completed' ? 'default' : 'primary'"
@click="handleAction(exam)"
>
{{ exam.status === 'completed' ? '查看' : (exam.status === 'in-progress' ? '批阅' : '查看') }}
</n-button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-container">
<n-pagination
v-model:page="currentPage"
:page-size="pageSize"
show-size-picker
:page-sizes="[10, 20, 50]"
:item-count="totalItems"
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { PersonOutline, CalendarOutline } from '@vicons/ionicons5'
// 接口定义
interface ExamItem {
id: string
title: string
description: string
creator: string
duration: string
status: 'not-started' | 'in-progress' | 'completed'
totalQuestions: number
submittedCount: number
gradedCount: number
}
// 路由
const router = useRouter()
const route = useRoute()
// 响应式数据
const activeTab = ref('all')
const examFilter = ref('')
const gradeFilter = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
// 选项数据
const examFilterOptions = [
{ label: '全部考试', value: '' },
{ label: '期中考试', value: 'midterm' },
{ label: '期末考试', value: 'final' },
{ label: '月考', value: 'monthly' }
]
const gradeFilterOptions = [
{ label: '全部班级', value: '' },
{ label: '一年级1班', value: 'grade1-1' },
{ label: '一年级2班', value: 'grade1-2' },
{ label: '二年级1班', value: 'grade2-1' }
]
// 模拟数据
const examList = ref<ExamItem[]>([
{
id: '1',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'not-started',
totalQuestions: 10,
submittedCount: 0,
gradedCount: 0
},
{
id: '2',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'in-progress',
totalQuestions: 0,
submittedCount: 0,
gradedCount: 0
},
{
id: '3',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'completed',
totalQuestions: 10,
submittedCount: 0,
gradedCount: 0
}
])
// 计算属性
const filteredExams = computed(() => {
let filtered = examList.value
// 根据tab过滤
if (activeTab.value !== 'all') {
filtered = filtered.filter(exam => exam.status === activeTab.value)
}
// 根据考试类型过滤
if (examFilter.value) {
// 这里可以添加具体的过滤逻辑
}
// 根据班级过滤
if (gradeFilter.value) {
// 这里可以添加具体的过滤逻辑
}
return filtered
})
const totalItems = computed(() => filteredExams.value.length)
// 方法
const handleTabChange = (value: string) => {
activeTab.value = value
currentPage.value = 1
}
const getStatusType = (status: string) => {
switch (status) {
case 'not-started':
return 'default'
case 'in-progress':
return 'warning'
case 'completed':
return 'success'
default:
return 'default'
}
}
const getStatusText = (status: string) => {
switch (status) {
case 'not-started':
return '未开始'
case 'in-progress':
return '进行中'
case 'completed':
return '已结束'
default:
return '未知'
}
}
const handleViewDetails = (exam: ExamItem) => {
console.log('查看试卷详情:', exam)
}
const handleDelete = (exam: ExamItem) => {
console.log('阅读预览:', exam)
}
const handleAction = (exam: ExamItem) => {
// 根据当前路由上下文决定跳转路径
const currentRoute = route.path;
if (currentRoute.includes('/course-editor/')) {
// 如果在课程编辑器中,使用 practice 路径
const courseId = route.params.id;
router.push(`/teacher/course-editor/${courseId}/practice/review/student-list/${exam.id}`);
} else {
// 如果在考试管理中,使用原有路径
router.push({
name: 'StudentList',
params: { paperId: exam.id }
});
}
}
const handlePageChange = (page: number) => {
currentPage.value = page
}
const handlePageSizeChange = (size: number) => {
pageSize.value = size
currentPage.value = 1
}
onMounted(() => {
// 组件挂载后的初始化操作
})
</script>
<style scoped>
/* Tab容器样式 */
.tab-container {
background-color: #fff;
padding: 16px 20px 0;
border-radius: 8px 8px 0 0;
}
:deep(.n-tabs-nav-scroll-content) {
--n-tab-font-size: 16px !important;
}
:deep(.n-tabs-tab) {
padding: 12px 24px;
margin-right: 8px;
}
:deep(.n-tabs-tab--active) {
color: #1890ff;
font-weight: 500;
}
/* 筛选栏样式 */
.filter-container {
display: flex;
justify-content: flex-end;
align-items: center;
background-color: #fff;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
}
.filter-right {
display: flex;
align-items: center;
}
/* 试卷列表样式 */
.exam-list {
background-color: #fff;
}
.exam-item {
display: flex;
align-items: center;
padding: 20px;
border-bottom: 1px solid #f0f0f0;
transition: all 0.3s ease;
}
.exam-item:hover {
background-color: #fafafa;
}
.exam-item.completed {
opacity: 0.8;
}
.exam-content {
flex: 1;
padding-right: 20px;
}
.exam-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.status-tag {
font-size: 12px;
padding: 2px 6px;
}
.exam-title {
font-size: 16px;
font-weight: 500;
color: #333;
line-height: 1.4;
}
.exam-description {
color: #666;
font-size: 14px;
line-height: 1.5;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.exam-meta {
display: flex;
gap: 20px;
margin-bottom: 12px;
}
.meta-item {
display: flex;
align-items: center;
gap: 4px;
color: #666;
font-size: 14px;
}
.meta-item .n-icon {
font-size: 16px;
}
.exam-actions {
display: flex;
gap: 16px;
}
/* 统计数据样式 */
.exam-stats {
display: flex;
gap: 30px;
margin-right: 20px;
}
.stats-item {
text-align: center;
}
.stats-number {
font-size: 20px;
font-weight: 600;
color: #333;
line-height: 1;
}
.stats-label {
font-size: 12px;
color: #999;
margin-top: 4px;
}
/* 操作按钮样式 */
.exam-action-button {
min-width: 80px;
}
.exam-action-button .n-button {
width: 100%;
}
/* 分页样式 */
.pagination-container {
background-color: #fff;
padding: 20px;
border-radius: 0 0 8px 8px;
display: flex;
justify-content: center;
}
/* 响应式设计 */
@media (max-width: 768px) {
.filter-container {
justify-content: center;
}
.filter-right {
width: 100%;
justify-content: space-between;
}
.exam-item {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.exam-stats {
width: 100%;
justify-content: space-around;
}
.exam-action-button {
width: 100%;
min-width: auto;
}
}
@media (max-width: 480px) {
.tab-container,
.filter-container,
.exam-list,
.pagination-container {
padding-left: 16px;
padding-right: 16px;
}
.exam-stats {
gap: 20px;
}
.meta-item {
font-size: 13px;
}
.exam-title {
font-size: 15px;
}
.exam-description {
font-size: 13px;
}
}
</style>