feat:添加课程-管理页面;考试管理菜单也同步添加到课程管理下;优化了一些页面
This commit is contained in:
parent
f888b0b458
commit
739ad411c1
@ -1,17 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal v-model:show="showModal" :mask-closable="false" preset="dialog" title="数据导入" style="width: 600px;">
|
<n-modal v-model:show="showModal" :mask-closable="false" preset="dialog" :title="title" style="width: 600px;">
|
||||||
<div class="import-modal-content">
|
<div class="import-modal-content">
|
||||||
<!-- 模板下载区域 -->
|
<!-- 模板下载区域 -->
|
||||||
<div class="template-section">
|
<div class="template-section">
|
||||||
<div class="section-title">
|
|
||||||
<n-icon size="18" color="#0288d1">
|
|
||||||
<CodeDownloadOutline />
|
|
||||||
</n-icon>
|
|
||||||
<span>模板下载</span>
|
|
||||||
</div>
|
|
||||||
<div class="template-description">
|
|
||||||
请先下载导入模板,按照模板格式填写数据后再上传
|
|
||||||
</div>
|
|
||||||
<n-button type="primary" ghost @click="downloadTemplate">
|
<n-button type="primary" ghost @click="downloadTemplate">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
@ -32,16 +23,9 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
<span>文件上传</span>
|
<span>文件上传</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-upload
|
<n-upload ref="uploadRef" :file-list="fileList" :max="1" accept=".xlsx,.xls" :show-file-list="false"
|
||||||
ref="uploadRef"
|
:custom-request="handleUpload" @change="handleFileChange">
|
||||||
:file-list="fileList"
|
|
||||||
:max="1"
|
|
||||||
accept=".xlsx,.xls"
|
|
||||||
:show-file-list="false"
|
|
||||||
:custom-request="handleUpload"
|
|
||||||
@change="handleFileChange"
|
|
||||||
>
|
|
||||||
<n-upload-dragger>
|
<n-upload-dragger>
|
||||||
<div class="upload-area">
|
<div class="upload-area">
|
||||||
<n-icon size="48" color="#0288d1" class="upload-icon">
|
<n-icon size="48" color="#0288d1" class="upload-icon">
|
||||||
@ -71,7 +55,7 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 上传进度 -->
|
<!-- 上传进度 -->
|
||||||
<div v-if="uploading" class="upload-progress">
|
<div v-if="uploading" class="upload-progress">
|
||||||
<n-progress :percentage="uploadProgress" :show-indicator="false" />
|
<n-progress :percentage="uploadProgress" :show-indicator="false" />
|
||||||
@ -95,32 +79,13 @@
|
|||||||
</n-alert>
|
</n-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 导入说明 -->
|
|
||||||
<div class="import-notes">
|
|
||||||
<div class="section-title">
|
|
||||||
<n-icon size="18" color="#fa8c16">
|
|
||||||
<InformationCircleOutline />
|
|
||||||
</n-icon>
|
|
||||||
<span>导入说明</span>
|
|
||||||
</div>
|
|
||||||
<ul class="notes-list">
|
|
||||||
<li>请严格按照模板格式填写数据,不要修改表头</li>
|
|
||||||
<li>必填字段不能为空,否则导入失败</li>
|
|
||||||
<li>导入前请检查数据格式,确保数据的准确性</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #action>
|
<template #action>
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-button @click="closeModal">取消</n-button>
|
<n-button @click="closeModal">取消</n-button>
|
||||||
<n-button
|
<n-button type="primary" @click="startImport" :disabled="!selectedFile || uploading"
|
||||||
type="primary"
|
:loading="importing">
|
||||||
@click="startImport"
|
|
||||||
:disabled="!selectedFile || uploading"
|
|
||||||
:loading="importing"
|
|
||||||
>
|
|
||||||
{{ importing ? '导入中...' : '开始导入' }}
|
{{ importing ? '导入中...' : '开始导入' }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
@ -130,19 +95,18 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import {
|
import {
|
||||||
CodeDownloadOutline,
|
DownloadOutline,
|
||||||
DownloadOutline,
|
CloudUploadOutline,
|
||||||
CloudUploadOutline,
|
DocumentTextOutline,
|
||||||
DocumentTextOutline,
|
CloseOutline,
|
||||||
CloseOutline,
|
|
||||||
InformationCircleOutline
|
|
||||||
} from '@vicons/ionicons5';
|
} from '@vicons/ionicons5';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import type { UploadFileInfo, UploadCustomRequestOptions } from 'naive-ui';
|
import type { UploadFileInfo, UploadCustomRequestOptions } from 'naive-ui';
|
||||||
|
|
||||||
// Props 定义
|
// Props 定义
|
||||||
interface Props {
|
interface Props {
|
||||||
|
title?: string;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
templateName?: string;
|
templateName?: string;
|
||||||
importType?: string;
|
importType?: string;
|
||||||
@ -156,6 +120,7 @@ interface Emits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: '导入数据',
|
||||||
templateName: 'import_template.xlsx',
|
templateName: 'import_template.xlsx',
|
||||||
importType: 'default'
|
importType: 'default'
|
||||||
});
|
});
|
||||||
@ -218,7 +183,7 @@ const handleFileChange = (options: { fileList: UploadFileInfo[] }) => {
|
|||||||
// 自定义上传处理
|
// 自定义上传处理
|
||||||
const handleUpload = (options: UploadCustomRequestOptions) => {
|
const handleUpload = (options: UploadCustomRequestOptions) => {
|
||||||
const { file } = options;
|
const { file } = options;
|
||||||
|
|
||||||
// 文件大小检查 (10MB)
|
// 文件大小检查 (10MB)
|
||||||
if (file.file && file.file.size > 10 * 1024 * 1024) {
|
if (file.file && file.file.size > 10 * 1024 * 1024) {
|
||||||
message.error('文件大小不能超过 10MB');
|
message.error('文件大小不能超过 10MB');
|
||||||
@ -246,7 +211,7 @@ const handleUpload = (options: UploadCustomRequestOptions) => {
|
|||||||
clearInterval(progressInterval);
|
clearInterval(progressInterval);
|
||||||
uploadProgress.value = 100;
|
uploadProgress.value = 100;
|
||||||
uploading.value = false;
|
uploading.value = false;
|
||||||
|
|
||||||
options.onFinish();
|
options.onFinish();
|
||||||
console.log('文件上传完成:', file.name);
|
console.log('文件上传完成:', file.name);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@ -273,7 +238,7 @@ const startImport = async () => {
|
|||||||
try {
|
try {
|
||||||
// 模拟导入过程
|
// 模拟导入过程
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
|
||||||
// 模拟导入结果
|
// 模拟导入结果
|
||||||
const mockResult: ImportResult = {
|
const mockResult: ImportResult = {
|
||||||
success: true,
|
success: true,
|
||||||
@ -285,20 +250,20 @@ const startImport = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
importResult.value = mockResult;
|
importResult.value = mockResult;
|
||||||
|
|
||||||
if (mockResult.success) {
|
if (mockResult.success) {
|
||||||
message.success('导入成功!');
|
message.success('导入成功!');
|
||||||
emit('success', mockResult);
|
emit('success', mockResult);
|
||||||
|
|
||||||
// 成功后延迟关闭弹窗
|
// 成功后延迟关闭弹窗
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closeModal();
|
closeModal();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('导入完成:', mockResult);
|
console.log('导入完成:', mockResult);
|
||||||
// TODO: 实现实际的导入API调用
|
// TODO: 实现实际的导入API调用
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
importResult.value = {
|
importResult.value = {
|
||||||
success: false,
|
success: false,
|
||||||
@ -319,7 +284,7 @@ const closeModal = () => {
|
|||||||
uploading.value = false;
|
uploading.value = false;
|
||||||
importing.value = false;
|
importing.value = false;
|
||||||
uploadProgress.value = 0;
|
uploadProgress.value = 0;
|
||||||
|
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -459,15 +424,15 @@ const closeModal = () => {
|
|||||||
.upload-area {
|
.upload-area {
|
||||||
padding: 24px 12px;
|
padding: 24px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-icon {
|
.upload-icon {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-description {
|
.template-description {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
398
src/components/teacher/ClassManagement.vue
Normal file
398
src/components/teacher/ClassManagement.vue
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
<template>
|
||||||
|
<div class="team-management">
|
||||||
|
<div class="toolbar">
|
||||||
|
<n-select v-model:value="selectedDepartment" :options="departmentOptions" placeholder="班级名称"
|
||||||
|
style="width: 200px" />
|
||||||
|
<NSpace>
|
||||||
|
<n-button type="primary" ghost>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon>
|
||||||
|
<AddCircleOutline />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
添加班级
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" ghost>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon>
|
||||||
|
<SettingsOutline />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
管理班级
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" ghost @click="showInviteModal = true">
|
||||||
|
<template #icon>
|
||||||
|
<NIcon>
|
||||||
|
<QrCode />
|
||||||
|
</NIcon>
|
||||||
|
</template>
|
||||||
|
邀请码
|
||||||
|
</n-button>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
<n-divider />
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="student-count">
|
||||||
|
全部学员{{ totalStudents }}人
|
||||||
|
</div>
|
||||||
|
<NSpace>
|
||||||
|
<n-button type="primary" @click="showAddModal = true">
|
||||||
|
添加学员
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" ghost>
|
||||||
|
导入
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary" ghost>
|
||||||
|
导出
|
||||||
|
</n-button>
|
||||||
|
<n-input v-model:value="searchKeyword" placeholder="请输入关键词" style="width: 200px" />
|
||||||
|
<n-button type="primary">
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-data-table :columns="columns" :data="data" :pagination="pagination" :loading="loading"
|
||||||
|
:row-key="(row: StudentItem) => row.id" striped size="small" />
|
||||||
|
|
||||||
|
<!-- 添加学员弹窗 -->
|
||||||
|
<n-modal v-model:show="showAddModal" title="添加学员">
|
||||||
|
<n-card style="width: 600px" title="添加学员" :bordered="false" size="huge" role="dialog" aria-modal="true">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="rules" label-placement="left" label-width="auto"
|
||||||
|
require-mark-placement="right-hanging">
|
||||||
|
<n-form-item label="姓名" path="teacherName">
|
||||||
|
<n-input v-model:value="formData.teacherName" placeholder="请输入学员姓名" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="学号" path="teacherId">
|
||||||
|
<n-input v-model:value="formData.teacherId" placeholder="请输入学员学号" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<n-button @click="showAddModal = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleSubmit">添加</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 邀请码弹窗 -->
|
||||||
|
<n-modal v-model:show="showInviteModal" title="邀请码">
|
||||||
|
<n-card style="width: 400px" title="邀请码" :bordered="false" size="huge" role="dialog" aria-modal="true">
|
||||||
|
<div class="invite-content">
|
||||||
|
<div class="invite-code-display">
|
||||||
|
<div class="invite-title">邀请码</div>
|
||||||
|
<div class="invite-code">{{ inviteCode }}</div>
|
||||||
|
<n-button ghost type="primary" @click="copyInviteCode">复制</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, h } from 'vue'
|
||||||
|
import { AddCircleOutline, SettingsOutline, QrCode } from '@vicons/ionicons5'
|
||||||
|
import {
|
||||||
|
NDataTable,
|
||||||
|
NButton,
|
||||||
|
NSpace,
|
||||||
|
NSelect,
|
||||||
|
NInput,
|
||||||
|
NModal,
|
||||||
|
NCard,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NIcon,
|
||||||
|
NDivider,
|
||||||
|
useMessage,
|
||||||
|
type FormInst,
|
||||||
|
type FormRules
|
||||||
|
} from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
// 定义数据类型
|
||||||
|
interface StudentItem {
|
||||||
|
id: string
|
||||||
|
studentName: string
|
||||||
|
accountNumber: string
|
||||||
|
className: string
|
||||||
|
college: string
|
||||||
|
loginName: string
|
||||||
|
joinTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单数据类型
|
||||||
|
interface FormData {
|
||||||
|
teacherName: string
|
||||||
|
teacherId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalStudents = ref(1333)
|
||||||
|
const inviteCode = ref('56685222')
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const selectedDepartment = ref('')
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const showAddModal = ref(false)
|
||||||
|
const showInviteModal = ref(false)
|
||||||
|
const formRef = ref<FormInst | null>(null)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref<FormData>({
|
||||||
|
teacherName: '',
|
||||||
|
teacherId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules: FormRules = {
|
||||||
|
teacherName: [
|
||||||
|
{ required: true, message: '请输入教师姓名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
teacherId: [
|
||||||
|
{ required: true, message: '请输入工号', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门选项
|
||||||
|
const departmentOptions = ref([
|
||||||
|
{ label: '默认班级', value: '' },
|
||||||
|
{ label: '软件工程1班', value: 'software1' },
|
||||||
|
{ label: '软件工程2班', value: 'software2' },
|
||||||
|
{ label: '计算机科学1班', value: 'cs1' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: DataTableColumns<StudentItem> = [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
key: 'studentName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '账号',
|
||||||
|
key: 'accountNumber',
|
||||||
|
width: 140,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '班级',
|
||||||
|
key: 'className',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所在学院',
|
||||||
|
key: 'college',
|
||||||
|
width: 200,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '登录名',
|
||||||
|
key: 'loginName',
|
||||||
|
width: 150,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '加入时间',
|
||||||
|
key: 'joinTime',
|
||||||
|
width: 140,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 200,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
NSpace,
|
||||||
|
{ size: 'small', justify: 'center' },
|
||||||
|
{
|
||||||
|
default: () => [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => handleEdit(row)
|
||||||
|
},
|
||||||
|
{ default: () => '编辑' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => handleReset(row)
|
||||||
|
},
|
||||||
|
{ default: () => '重置' }
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => handleDelete(row)
|
||||||
|
},
|
||||||
|
{ default: () => '移除' }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const data = ref<StudentItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
onChange: (page: number) => {
|
||||||
|
pagination.value.page = page
|
||||||
|
loadData()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize: number) => {
|
||||||
|
pagination.value.pageSize = pageSize
|
||||||
|
pagination.value.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 操作处理函数
|
||||||
|
const handleEdit = (row: StudentItem) => {
|
||||||
|
message.info(`编辑学员:${row.studentName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = (row: StudentItem) => {
|
||||||
|
message.info(`重置学员密码:${row.studentName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (row: StudentItem) => {
|
||||||
|
message.warning(`移除学员:${row.studentName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyInviteCode = () => {
|
||||||
|
navigator.clipboard.writeText(inviteCode.value).then(() => {
|
||||||
|
message.success('邀请码已复制到剪贴板')
|
||||||
|
}).catch(() => {
|
||||||
|
message.error('复制失败,请手动复制')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
// 这里处理添加教师的逻辑
|
||||||
|
message.success('添加教师成功')
|
||||||
|
showAddModal.value = false
|
||||||
|
// 重置表单
|
||||||
|
formData.value = {
|
||||||
|
teacherName: '',
|
||||||
|
teacherId: ''
|
||||||
|
}
|
||||||
|
// 重新加载数据
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
message.error('请检查表单信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据加载
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
|
||||||
|
const mockData: StudentItem[] = Array.from({ length: 3 }, (_, index) => ({
|
||||||
|
id: `student_${index + 1}`,
|
||||||
|
studentName: ['张华', '李明', '王丽'][index],
|
||||||
|
accountNumber: [`${(1660340 + index + 1).toString()}`, `${(1660340 + index + 2).toString()}`, `${(1660340 + index + 3).toString()}`][index],
|
||||||
|
className: '计算机1',
|
||||||
|
college: '清华大学经管学院',
|
||||||
|
loginName: [`${(1660340 + index + 1).toString()}`, `${(1660340 + index + 2).toString()}`, `${(1660340 + index + 3).toString()}`][index],
|
||||||
|
joinTime: '2025.07.25 08:20'
|
||||||
|
}))
|
||||||
|
|
||||||
|
data.value = mockData
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.team-management {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
margin: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-count {
|
||||||
|
color: #838383;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background-color: #F5F8FB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-code-display {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-title {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-code {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1890ff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-note {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
134
src/components/teacher/OperationLog.vue
Normal file
134
src/components/teacher/OperationLog.vue
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<template>
|
||||||
|
<div class="operation-log">
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:pagination="pagination"
|
||||||
|
:loading="loading"
|
||||||
|
striped
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { NDataTable } from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
// 定义数据类型
|
||||||
|
interface LogItem {
|
||||||
|
id: string
|
||||||
|
role: string
|
||||||
|
userName: string
|
||||||
|
studentId: string
|
||||||
|
operationContent: string
|
||||||
|
operatorIp: string
|
||||||
|
operationTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: DataTableColumns<LogItem> = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'id',
|
||||||
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
|
render: (_, index) => index + 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色',
|
||||||
|
key: 'role',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
key: 'userName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '学号',
|
||||||
|
key: 'studentId',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作内容',
|
||||||
|
key: 'operationContent',
|
||||||
|
minWidth: 200,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作IP',
|
||||||
|
key: 'operatorIp',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
key: 'operationTime',
|
||||||
|
width: 180,
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const data = ref<LogItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
onChange: (page: number) => {
|
||||||
|
pagination.value.page = page
|
||||||
|
loadData()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize: number) => {
|
||||||
|
pagination.value.pageSize = pageSize
|
||||||
|
pagination.value.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模拟数据加载
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 模拟API调用延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
|
||||||
|
// 生成模拟数据
|
||||||
|
const mockData: LogItem[] = Array.from({ length: 15 }, (_, index) => ({
|
||||||
|
id: `${index + 1}`,
|
||||||
|
role: '教师',
|
||||||
|
userName: '刘桂人',
|
||||||
|
studentId: '56566652',
|
||||||
|
operationContent: '操作内容',
|
||||||
|
operatorIp: 'ip地址',
|
||||||
|
operationTime: '2025.08.02 07:30'
|
||||||
|
}))
|
||||||
|
|
||||||
|
data.value = mockData
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.operation-log {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
</style>
|
262
src/components/teacher/TeamManagement.vue
Normal file
262
src/components/teacher/TeamManagement.vue
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<div class="team-management">
|
||||||
|
<div class="toolbar">
|
||||||
|
<n-select v-model:value="selectedDepartment" :options="departmentOptions" placeholder="班级名称"
|
||||||
|
style="width: 200px" />
|
||||||
|
<NSpace>
|
||||||
|
<n-button type="primary" @click="showAddModal = true">
|
||||||
|
添加老师
|
||||||
|
</n-button>
|
||||||
|
<n-input v-model:value="searchKeyword" placeholder="请输入关键词" style="width: 200px" />
|
||||||
|
<n-button type="primary">
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-data-table :columns="columns" :data="data" :pagination="pagination" :loading="loading"
|
||||||
|
:row-key="(row: TeacherItem) => row.id" striped size="small" />
|
||||||
|
|
||||||
|
<!-- 添加教师弹窗 -->
|
||||||
|
<n-modal v-model:show="showAddModal" title="添加老师">
|
||||||
|
<n-card style="width: 600px" title="添加老师" :bordered="false" size="huge" role="dialog" aria-modal="true">
|
||||||
|
<n-form ref="formRef" :model="formData" :rules="rules" label-placement="left" label-width="auto"
|
||||||
|
require-mark-placement="right-hanging">
|
||||||
|
<n-form-item label="姓名" path="teacherName">
|
||||||
|
<n-input v-model:value="formData.teacherName" placeholder="请输入老师姓名" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="工号" path="teacherId">
|
||||||
|
<n-input v-model:value="formData.teacherId" placeholder="请输入老师工号" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<n-button @click="showAddModal = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="handleSubmit">添加</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, h } from 'vue'
|
||||||
|
import {
|
||||||
|
NDataTable,
|
||||||
|
NButton,
|
||||||
|
NSpace,
|
||||||
|
NSelect,
|
||||||
|
NInput,
|
||||||
|
NModal,
|
||||||
|
NCard,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
useMessage,
|
||||||
|
type FormInst,
|
||||||
|
type FormRules
|
||||||
|
} from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
// 定义数据类型
|
||||||
|
interface TeacherItem {
|
||||||
|
id: string
|
||||||
|
teacherName: string
|
||||||
|
teacherId: string
|
||||||
|
role: string
|
||||||
|
studentId: string
|
||||||
|
teachingClass: string
|
||||||
|
department: string
|
||||||
|
joinTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单数据类型
|
||||||
|
interface FormData {
|
||||||
|
teacherName: string
|
||||||
|
teacherId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const selectedDepartment = ref('')
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const showAddModal = ref(false)
|
||||||
|
const formRef = ref<FormInst | null>(null)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref<FormData>({
|
||||||
|
teacherName: '',
|
||||||
|
teacherId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules: FormRules = {
|
||||||
|
teacherName: [
|
||||||
|
{ required: true, message: '请输入教师姓名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
teacherId: [
|
||||||
|
{ required: true, message: '请输入工号', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门选项
|
||||||
|
const departmentOptions = ref([
|
||||||
|
{ label: '全部班级', value: '' },
|
||||||
|
{ label: '软件工程1班', value: 'software1' },
|
||||||
|
{ label: '软件工程2班', value: 'software2' },
|
||||||
|
{ label: '计算机科学1班', value: 'cs1' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: DataTableColumns<TeacherItem> = [
|
||||||
|
{
|
||||||
|
title: '姓名',
|
||||||
|
key: 'teacherName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色',
|
||||||
|
key: 'role',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '成员',
|
||||||
|
key: 'studentId',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '任教班级',
|
||||||
|
key: 'teachingClass',
|
||||||
|
width: 150,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属机构',
|
||||||
|
key: 'department',
|
||||||
|
width: 150,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '加入时间',
|
||||||
|
key: 'joinTime',
|
||||||
|
width: 140,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => handleDelete(row)
|
||||||
|
},
|
||||||
|
{ default: () => '移除' }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const data = ref<TeacherItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const pagination = ref({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
onChange: (page: number) => {
|
||||||
|
pagination.value.page = page
|
||||||
|
loadData()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (pageSize: number) => {
|
||||||
|
pagination.value.pageSize = pageSize
|
||||||
|
pagination.value.page = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 操作处理函数
|
||||||
|
const handleDelete = (row: TeacherItem) => {
|
||||||
|
message.warning(`移除教师:${row.teacherName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
// 这里处理添加教师的逻辑
|
||||||
|
message.success('添加教师成功')
|
||||||
|
showAddModal.value = false
|
||||||
|
// 重置表单
|
||||||
|
formData.value = {
|
||||||
|
teacherName: '',
|
||||||
|
teacherId: ''
|
||||||
|
}
|
||||||
|
// 重新加载数据
|
||||||
|
loadData()
|
||||||
|
} catch (error) {
|
||||||
|
message.error('请检查表单信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟数据加载
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
|
||||||
|
const roles = ['刘桂人']
|
||||||
|
const departments = ['北京大学经济学理学院']
|
||||||
|
const mockData: TeacherItem[] = Array.from({ length: 1 }, (_, index) => ({
|
||||||
|
id: `teacher_${index + 1}`,
|
||||||
|
teacherName: roles[0],
|
||||||
|
teacherId: `56566652`,
|
||||||
|
role: '刘桂人',
|
||||||
|
studentId: '56566652',
|
||||||
|
teachingClass: '北京大学经济学理学院',
|
||||||
|
department: departments[0],
|
||||||
|
joinTime: '2025.08.02 07:30'
|
||||||
|
}))
|
||||||
|
|
||||||
|
data.value = mockData
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.team-management {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer{
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -197,18 +197,20 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: 'practice',
|
path: 'practice',
|
||||||
name: 'Practice',
|
name: 'Practice',
|
||||||
redirect: (to) => `/teacher/course-editor/${to.params.id}/practice/exam`,
|
redirect: (to) => `/teacher/course-editor/${to.params.id}/practice/exam`,
|
||||||
meta: { title: '练考通' },
|
meta: { title: '考试管理' },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'exam',
|
path: 'exam',
|
||||||
name: 'PracticeExam',
|
name: 'PracticeExam',
|
||||||
component: () => import('../views/teacher/course/PracticeExam.vue'),
|
// component: () => import('../views/teacher/course/PracticeExam.vue'),
|
||||||
|
component: ExamLibrary,
|
||||||
meta: { title: '试卷' }
|
meta: { title: '试卷' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'review',
|
path: 'review',
|
||||||
name: 'PracticeReview',
|
name: 'PracticeReview',
|
||||||
component: () => import('../views/teacher/course/PracticeReview.vue'),
|
// component: () => import('../views/teacher/course/PracticeReview.vue'),
|
||||||
|
component: MarkingCenter,
|
||||||
meta: { title: '阅卷中心' }
|
meta: { title: '阅卷中心' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -216,7 +218,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'question-bank',
|
path: 'question-bank',
|
||||||
name: 'QuestionBankManagement',
|
name: 'QuestionBankManagement',
|
||||||
component: QuestionBankManagement,
|
component: ExamQuestionBankManagement,
|
||||||
meta: { title: '题库管理' }
|
meta: { title: '题库管理' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -27,7 +27,9 @@
|
|||||||
|
|
||||||
<!-- 考试管理 - 可展开菜单 -->
|
<!-- 考试管理 - 可展开菜单 -->
|
||||||
<div class="nav-item" :class="{ active: activeNavItem === 4 }" @click="toggleExamMenu">
|
<div class="nav-item" :class="{ active: activeNavItem === 4 }" @click="toggleExamMenu">
|
||||||
<img :src="activeNavItem === 4 ? '/images/teacher/examination-active.png' : '/images/teacher/examination.png'" alt="">
|
<img
|
||||||
|
:src="activeNavItem === 4 ? '/images/teacher/examination-active.png' : '/images/teacher/examination.png'"
|
||||||
|
alt="">
|
||||||
<span>考试管理</span>
|
<span>考试管理</span>
|
||||||
<n-icon class="expand-icon" :class="{ expanded: examMenuExpanded }">
|
<n-icon class="expand-icon" :class="{ expanded: examMenuExpanded }">
|
||||||
<ChevronDownOutline />
|
<ChevronDownOutline />
|
||||||
@ -37,8 +39,7 @@
|
|||||||
<!-- 考试管理子菜单 -->
|
<!-- 考试管理子菜单 -->
|
||||||
<div class="submenu-container" :class="{ expanded: examMenuExpanded }">
|
<div class="submenu-container" :class="{ expanded: examMenuExpanded }">
|
||||||
<router-link to="/teacher/exam-management/question-bank" class="submenu-item"
|
<router-link to="/teacher/exam-management/question-bank" class="submenu-item"
|
||||||
:class="{ active: activeSubNavItem === 'question-bank' }"
|
:class="{ active: activeSubNavItem === 'question-bank' }" @click="setActiveSubNavItem('question-bank')">
|
||||||
@click="setActiveSubNavItem('question-bank')">
|
|
||||||
<span>题库管理</span>
|
<span>题库管理</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/teacher/exam-management/exam-library" class="submenu-item"
|
<router-link to="/teacher/exam-management/exam-library" class="submenu-item"
|
||||||
@ -349,10 +350,10 @@ const breadcrumbPathItems = computed(() => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (currentPath.includes('practice')) {
|
} else if (currentPath.includes('practice/exam')) {
|
||||||
breadcrumbs.push(
|
breadcrumbs.push(
|
||||||
{
|
{
|
||||||
title: '练考通',
|
title: '考试管理',
|
||||||
path: currentPath
|
path: currentPath
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -360,9 +361,10 @@ const breadcrumbPathItems = computed(() => {
|
|||||||
path: `/teacher/course-editor/${courseId}`
|
path: `/teacher/course-editor/${courseId}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else if (currentPath.includes('practice/review')) {
|
||||||
breadcrumbs.push(
|
breadcrumbs.push(
|
||||||
{
|
{
|
||||||
title: '练考通',
|
title: '阅卷中心',
|
||||||
path: currentPath
|
path: currentPath
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -370,6 +372,7 @@ const breadcrumbPathItems = computed(() => {
|
|||||||
path: `/teacher/course-editor/${courseId}`
|
path: `/teacher/course-editor/${courseId}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
} else if (currentPath.includes('certificate')) {
|
} else if (currentPath.includes('certificate')) {
|
||||||
breadcrumbs.push(
|
breadcrumbs.push(
|
||||||
{
|
{
|
||||||
@ -505,7 +508,7 @@ const updateActiveNavItem = () => {
|
|||||||
} else if (path.includes('exam-management')) {
|
} else if (path.includes('exam-management')) {
|
||||||
activeNavItem.value = 4; // 考试管理
|
activeNavItem.value = 4; // 考试管理
|
||||||
examMenuExpanded.value = true;
|
examMenuExpanded.value = true;
|
||||||
|
|
||||||
const arr = ['question-bank', 'exam-library', 'marking-center'];
|
const arr = ['question-bank', 'exam-library', 'marking-center'];
|
||||||
const found = arr.find(item => path.includes(item));
|
const found = arr.find(item => path.includes(item));
|
||||||
activeSubNavItem.value = found || '';
|
activeSubNavItem.value = found || '';
|
||||||
@ -890,6 +893,7 @@ const updateActiveNavItem = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item {
|
.breadcrumb-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -61,6 +61,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ImportModal
|
||||||
|
v-model:show="showImportModal"
|
||||||
|
template-name="custom_template.xlsx"
|
||||||
|
import-type="custom"
|
||||||
|
@success="handleImportSuccess"
|
||||||
|
@template-download="handleTemplateDownload" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -69,6 +75,7 @@ import { ref, computed, h } from 'vue'
|
|||||||
import { NButton, useMessage, NDataTable, NInput, NSpace } from 'naive-ui'
|
import { NButton, useMessage, NDataTable, NInput, NSpace } from 'naive-ui'
|
||||||
import type { DataTableColumns } from 'naive-ui'
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import ImportModal from '@/components/common/ImportModal.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -88,6 +95,16 @@ interface Chapter {
|
|||||||
expanded?: boolean
|
expanded?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const showImportModal = ref(false)
|
||||||
|
|
||||||
|
const handleImportSuccess = () => {
|
||||||
|
message.success('章节导入成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTemplateDownload = () => {
|
||||||
|
message.success('模板下载成功')
|
||||||
|
}
|
||||||
// 搜索关键词
|
// 搜索关键词
|
||||||
const searchKeyword = ref('')
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
@ -351,7 +368,7 @@ const addChapter = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const importChapters = () => {
|
const importChapters = () => {
|
||||||
message.info('导入章节功能')
|
showImportModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportChapters = () => {
|
const exportChapters = () => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<div class="menu-header" @click="toggleHomework">
|
<div class="menu-header" @click="toggleHomework">
|
||||||
<img src="/images/teacher/作业.png" alt="作业" />
|
<img src="/images/teacher/作业.png" alt="作业" />
|
||||||
<span>作业</span>
|
<span>作业</span>
|
||||||
<i class="n-base-icon" :class="{ expanded: homeworkExpanded || shouldExpandHomework }">
|
<i class="n-base-icon" :class="{ expanded: homeworkExpanded }">
|
||||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
|
d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="submenu" v-show="homeworkExpanded || shouldExpandHomework">
|
<div class="submenu" v-show="homeworkExpanded">
|
||||||
<router-link :to="`/teacher/course-editor/${courseId}/homework/library`" class="submenu-item"
|
<router-link :to="`/teacher/course-editor/${courseId}/homework/library`" class="submenu-item"
|
||||||
:class="{ active: $route.path.includes('/homework/library') || $route.path.includes('/homework/add-homework') }">
|
:class="{ active: $route.path.includes('/homework/library') || $route.path.includes('/homework/add-homework') }">
|
||||||
<span>作业库</span>
|
<span>作业库</span>
|
||||||
@ -53,6 +53,10 @@
|
|||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="submenu" v-show="practiceExpanded">
|
<div class="submenu" v-show="practiceExpanded">
|
||||||
|
<router-link :to="`/teacher/course-editor/${courseId}/question-bank`" class="submenu-item"
|
||||||
|
:class="{ active: $route.path.includes('/question-bank') }">
|
||||||
|
<span>题库管理</span>
|
||||||
|
</router-link>
|
||||||
<router-link :to="`/teacher/course-editor/${courseId}/practice/exam`" class="submenu-item"
|
<router-link :to="`/teacher/course-editor/${courseId}/practice/exam`" class="submenu-item"
|
||||||
:class="{ active: $route.path.includes('/practice/exam') }">
|
:class="{ active: $route.path.includes('/practice/exam') }">
|
||||||
<span>试卷管理</span>
|
<span>试卷管理</span>
|
||||||
@ -63,12 +67,6 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <router-link :to="`/teacher/course-editor/${courseId}/question-bank`" class="menu-item"
|
|
||||||
:class="{ active: $route.path.includes('question-bank') }">
|
|
||||||
<img :src="$route.path.includes('question-bank') ? '/images/teacher/题库-选中.png' : '/images/teacher/题库.png'"
|
|
||||||
alt="题库" />
|
|
||||||
<span>题库</span>
|
|
||||||
</router-link> -->
|
|
||||||
<router-link :to="`/teacher/course-editor/${courseId}/certificate`" class="menu-item"
|
<router-link :to="`/teacher/course-editor/${courseId}/certificate`" class="menu-item"
|
||||||
:class="{ active: $route.path.includes('certificate') }">
|
:class="{ active: $route.path.includes('certificate') }">
|
||||||
<img :src="$route.path.includes('certificate') ? '/images/teacher/证书-选中.png' : '/images/teacher/证书.png'"
|
<img :src="$route.path.includes('certificate') ? '/images/teacher/证书-选中.png' : '/images/teacher/证书.png'"
|
||||||
@ -124,11 +122,6 @@ const courseId = route.params.id
|
|||||||
// 作业菜单展开状态
|
// 作业菜单展开状态
|
||||||
const homeworkExpanded = ref(false)
|
const homeworkExpanded = ref(false)
|
||||||
|
|
||||||
// 根据当前路由自动展开作业菜单
|
|
||||||
const shouldExpandHomework = computed(() => {
|
|
||||||
return route.path.includes('/homework/')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 切换作业菜单展开/收起
|
// 切换作业菜单展开/收起
|
||||||
const toggleHomework = () => {
|
const toggleHomework = () => {
|
||||||
homeworkExpanded.value = !homeworkExpanded.value
|
homeworkExpanded.value = !homeworkExpanded.value
|
||||||
@ -142,6 +135,12 @@ const togglePractice = () => {
|
|||||||
practiceExpanded.value = !practiceExpanded.value
|
practiceExpanded.value = !practiceExpanded.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.path.includes('/homework/')) {
|
||||||
|
homeworkExpanded.value = true
|
||||||
|
}else if(route.path.includes('/practice/') || route.path.includes('/question-bank')){
|
||||||
|
practiceExpanded.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否隐藏左侧导航
|
// 判断是否隐藏左侧导航
|
||||||
const hideSidebar = computed(() => {
|
const hideSidebar = computed(() => {
|
||||||
const currentPath = route.path
|
const currentPath = route.path
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="general-management">
|
<div class="general-management">
|
||||||
<div class="content-placeholder">
|
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||||
<h2>综合管理</h2>
|
<n-tab-pane name="class" tab="班级管理">
|
||||||
<p>综合管理功能正在开发中...</p>
|
<ClassManagement />
|
||||||
</div>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="team" tab="教师团队管理">
|
||||||
|
<TeamManagement />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="log" tab="操作日志">
|
||||||
|
<OperationLog />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 综合管理逻辑
|
import { ref } from 'vue'
|
||||||
|
import { NTabs, NTabPane } from 'naive-ui'
|
||||||
|
import ClassManagement from '@/components/teacher/ClassManagement.vue'
|
||||||
|
import TeamManagement from '@/components/teacher/TeamManagement.vue'
|
||||||
|
import OperationLog from '@/components/teacher/OperationLog.vue'
|
||||||
|
|
||||||
|
// 当前激活的tab
|
||||||
|
const activeTab = ref('class')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -18,19 +32,22 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder {
|
/* 自定义tab样式 */
|
||||||
text-align: center;
|
:deep(.n-tabs .n-tabs-nav) {
|
||||||
padding: 60px 20px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder h2 {
|
:deep(.n-tabs .n-tabs-tab) {
|
||||||
font-size: 24px;
|
padding: 12px 20px;
|
||||||
color: #333;
|
font-size: 14px;
|
||||||
margin-bottom: 20px;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder p {
|
:deep(.n-tabs .n-tabs-tab.n-tabs-tab--active) {
|
||||||
font-size: 16px;
|
color: #1890ff;
|
||||||
color: #666;
|
}
|
||||||
|
|
||||||
|
:deep(.n-tabs .n-tabs-bar) {
|
||||||
|
background-color: #1890ff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user