This commit is contained in:
Wxp 2025-08-21 19:41:58 +08:00
commit 2f13f5da8b
42 changed files with 3581 additions and 2742 deletions

2382
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

View File

@ -1,7 +1,7 @@
<template>
<div class="course-category">
<!-- 顶部 -->
<div class="top">
<div class="top">
<div class="nav-links">
<a href="" class="active">全部</a>
<a href="">发布中</a>
@ -16,61 +16,64 @@
</div>
</div>
</div>
</div>
<!-- 主体 -->
<div class="course-container">
<!-- 主体 -->
<div class="course-container">
<div class="course-grid">
<div class="course-card" v-for="course in courseList" :key="course.id" @click="navigateToCourseDetail(course.id)">
<div class="course-image-container">
<div class="section-title" :class="{'offline': course.status === '下架中'}">{{ course.status }}</div>
<div class="more-options">
<span class="more-icon"></span>
<div class="options-menu">
<template v-if="course.status === '发布中'">
<a href="#" class="option-item"><img src="/images/teacher/下架.png" alt="">下架</a>
<a href="#" class="option-item"><img src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="#" class="option-item"><img src="/images/teacher/移动.png" alt="">移动</a>
<a href="#" class="option-item"><img src="/images/teacher/删除.png" alt="">删除</a>
</template>
<template v-else-if="course.status === '下架中'">
<a href="#" class="option-item"><img src="/images/teacher/加号.png" alt="">发布</a>
<a href="#" class="option-item"><img src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="#" class="option-item"><img src="/images/teacher/移动.png" alt="">移动</a>
<a href="#" class="option-item"><img src="/images/teacher/删除.png" alt="">删除</a>
</template>
</div>
<div class="course-card" v-for="course in courseList" :key="course.id">
<div class="course-image-container">
<div class="section-title" :class="{ 'offline': course.status === '下架中' }">{{ course.status }}
</div>
<div class="more-options">
<span class="more-icon"></span>
<div class="options-menu">
<template v-if="course.status === '发布中'">
<a href="#" class="option-item"><img src="/images/teacher/下架.png" alt="">下架</a>
<a href="#" class="option-item" @click.prevent="editCourse(course.id)"><img
src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="#" class="option-item"><img src="/images/teacher/移动.png" alt="">移动</a>
<a href="#" class="option-item"><img src="/images/teacher/删除.png" alt="">删除</a>
</template>
<template v-else-if="course.status === '下架中'">
<a href="#" class="option-item"><img src="/images/teacher/加号.png" alt="">发布</a>
<a href="#" class="option-item" @click.prevent="editCourse(course.id)"><img
src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="#" class="option-item"><img src="/images/teacher/移动.png" alt="">移动</a>
<a href="#" class="option-item"><img src="/images/teacher/删除.png" alt="">删除</a>
</template>
</div>
</div>
</div>
<div class="course-info">
<img :src="course.image" alt="">
<a href="#" class="course-name">{{ course.name }}</a>
</div>
</div>
<div class="course-info">
<img :src="course.image" alt="">
<a href="#" class="course-name">{{ course.name }}</a>
</div>
</div>
</div>
</div>
</div>
<!-- 底部翻页按钮 -->
<div class="pagination">
<!-- 底部翻页按钮 -->
<div class="pagination">
<div class="pagination-content">
<div class="page-numbers">
<a href="#" class="page-number">首页</a>
<a href="#" class="page-number">上一页</a>
<a href="#" class="page-number page-number-bordered active">1</a>
<a href="#" class="page-number page-number-bordered">2</a>
<a href="#" class="page-number page-number-bordered">3</a>
<a href="#" class="page-number page-number-bordered">4</a>
<a href="#" class="page-number page-number-bordered">5</a>
<a href="#" class="page-number">...</a>
<a href="#" class="page-number page-number-bordered">29</a>
<a href="#" class="page-number page-number-bordered">下一页</a>
<a href="#" class="page-number">下一页</a>
<a href="#" class="page-number">尾页</a>
</div>
<div class="page-numbers">
<a href="#" class="page-number">首页</a>
<a href="#" class="page-number">上一页</a>
<a href="#" class="page-number page-number-bordered active">1</a>
<a href="#" class="page-number page-number-bordered">2</a>
<a href="#" class="page-number page-number-bordered">3</a>
<a href="#" class="page-number page-number-bordered">4</a>
<a href="#" class="page-number page-number-bordered">5</a>
<a href="#" class="page-number">...</a>
<a href="#" class="page-number page-number-bordered">29</a>
<a href="#" class="page-number page-number-bordered">下一页</a>
<a href="#" class="page-number">下一页</a>
<a href="#" class="page-number">尾页</a>
</div>
</div>
</div>
</div>
</div>
</template>
@ -78,32 +81,33 @@
import { ref } from 'vue';
import { useRouter } from 'vue-router';
//
const router = useRouter();
const navigateToCreateCourse = () => {
router.push('/teacher/course-create');
}
//
const navigateToCourseDetail = (courseId: number) => {
router.push(`/teacher/course-detail?id=${courseId}`);
}
//
const courseList = ref([
{ id: 1, name: '前端开发基础课程', status: '发布中', image: 'https://picsum.photos/200/200?random=1' },
{ id: 2, name: 'Vue.js 实战教程', status: '发布中', image: 'https://picsum.photos/200/200?random=2' },
{ id: 3, name: 'React 入门到精通', status: '发布中', image: 'https://picsum.photos/200/200?random=3' },
{ id: 4, name: 'Node.js 后端开发', status: '下架中', image: 'https://picsum.photos/200/200?random=4' },
{ id: 5, name: 'TypeScript 高级教程', status: '发布中', image: 'https://picsum.photos/200/200?random=5' },
{ id: 6, name: 'JavaScript 设计模式', status: '发布中', image: 'https://picsum.photos/200/200?random=6' },
{ id: 7, name: 'CSS 动画与特效', status: '下架中', image: 'https://picsum.photos/200/200?random=7' },
{ id: 8, name: 'HTML5 新特性详解', status: '发布中', image: 'https://picsum.photos/200/200?random=8' },
{ id: 9, name: 'Web 性能优化指南', status: '发布中', image: 'https://picsum.photos/200/200?random=9' },
{ id: 10, name: '移动端适配实战', status: '发布中', image: 'https://picsum.photos/200/200?random=10' },
{ id: 11, name: '微信小程序开发', status: '下架中', image: 'https://picsum.photos/200/200?random=11' },
{ id: 12, name: 'Flutter 跨平台开发', status: '发布中', image: 'https://picsum.photos/200/200?random=12' },
{ id: 1, name: '前端开发基础课程', status: '发布中', image: 'https://picsum.photos/200/200?random=1' },
{ id: 2, name: 'Vue.js 实战教程', status: '发布中', image: 'https://picsum.photos/200/200?random=2' },
{ id: 3, name: 'React 入门到精通', status: '发布中', image: 'https://picsum.photos/200/200?random=3' },
{ id: 4, name: 'Node.js 后端开发', status: '下架中', image: 'https://picsum.photos/200/200?random=4' },
{ id: 5, name: 'TypeScript 高级教程', status: '发布中', image: 'https://picsum.photos/200/200?random=5' },
{ id: 6, name: 'JavaScript 设计模式', status: '发布中', image: 'https://picsum.photos/200/200?random=6' },
{ id: 7, name: 'CSS 动画与特效', status: '下架中', image: 'https://picsum.photos/200/200?random=7' },
{ id: 8, name: 'HTML5 新特性详解', status: '发布中', image: 'https://picsum.photos/200/200?random=8' },
{ id: 9, name: 'Web 性能优化指南', status: '发布中', image: 'https://picsum.photos/200/200?random=9' },
{ id: 10, name: '移动端适配实战', status: '发布中', image: 'https://picsum.photos/200/200?random=10' },
{ id: 11, name: '微信小程序开发', status: '下架中', image: 'https://picsum.photos/200/200?random=11' },
{ id: 12, name: 'Flutter 跨平台开发', status: '发布中', image: 'https://picsum.photos/200/200?random=12' },
]);
//
const editCourse = (courseId: number) => {
router.push(`/teacher/course-editor/${courseId}`);
};
//
const navigateToCreateCourse = () => {
router.push('/teacher/course-create');
};
</script>

View File

@ -1,14 +1,511 @@
<template>
<div>
<h1>课程创建</h1>
<div class="page">
<div class="course-form-container flex-col">
<!-- 第一行课程名称和课程分类 -->
<div class="form-row-name-category flex-row">
<div class="label-required">
<span class="text_17">*</span>
<span class="text_18">课程名称</span>
</div>
<div class="course-name-input-wrapper flex-col">
<n-input
v-model:value="formData.courseName"
placeholder="请输入课程名称"
class="form-input"
:bordered="false"
/>
</div>
<div class="category-section flex-col">
<div class="label-category">
<span class="text_20">*</span>
<span class="text_21">课程分类</span>
</div>
<div class="course-category-select-wrapper flex-col">
<n-select
v-model:value="formData.courseCategory"
placeholder="分类名称"
:options="categoryOptions"
class="form-select"
:bordered="false"
/>
</div>
</div>
</div>
<!-- 第二行主讲老师和排序 -->
<div class="form-row-instructor flex-row">
<div class="label-instructor">
<span class="text_23">*</span>
<span class="text_24">主讲老师</span>
</div>
<div class="form-select-wrapper flex-row">
<n-select
v-model:value="formData.instructors"
multiple
placeholder="请选择主讲老师"
:options="instructorOptions"
class="form-select"
:bordered="false"
:render-tag="renderInstructorTag"
/>
</div>
<div class="sort-section flex-col">
<div class="label-sort">
<span class="text_28">*</span>
<span class="text_29">排序</span>
</div>
</div>
</div>
<!-- 排序输入框 -->
<div class="form-input-wrapper">
<n-input
v-model:value="formData.sort"
placeholder="数字越小越排序靠前"
class="form-input"
:bordered="false"
/>
</div>
<!-- 第三行课程开始时间和结束时间 -->
<div class="form-row-date flex-row">
<div class="label-date">
<span class="text_30">*</span>
<span class="text_31">课程开始时间</span>
</div>
<div class="start-date-wrapper flex-row">
<n-date-picker
v-model:value="formData.startTime"
type="datetime"
placeholder="选择时间"
class="form-datepicker"
:bordered="false"
/>
</div>
<div class="label-end-date">
<span class="text_33">*</span>
<span class="text_34">课程结束时间</span>
</div>
<div class="end-date-wrapper flex-row">
<n-date-picker
v-model:value="formData.endTime"
type="datetime"
placeholder="选择时间"
class="form-datepicker"
:bordered="false"
/>
</div>
</div>
<!-- 第四行参与学员 -->
<div class="form-row-students flex-row">
<div class="label-students">
<span class="text_36">*</span>
<span class="text_37">参与学员</span>
</div>
<div class="radio-group flex-row">
<label class="radio-option" @click="formData.studentType = 'all'">
<div class="radio-circle" :class="{ active: formData.studentType === 'all' }">
<div class="radio-dot" v-if="formData.studentType === 'all'"></div>
</div>
<span class="text_38">全部学员</span>
</label>
<label class="radio-option" @click="formData.studentType = 'partial'">
<div class="radio-circle" :class="{ active: formData.studentType === 'partial' }">
<div class="radio-dot" v-if="formData.studentType === 'partial'"></div>
</div>
<span class="text_39">仅部分学员</span>
</label>
</div>
</div>
<!-- 第五行选择班级仅在选择部分学员时显示 -->
<div class="form-row-class" v-if="formData.studentType === 'partial'">
<div class="form-select-wrapper flex-row">
<n-select
v-model:value="formData.selectedClasses"
multiple
placeholder="选择班级(可多选)"
:options="classOptions"
class="form-select"
:bordered="false"
/>
</div>
</div>
<!-- 第六行课程封面 -->
<div class="form-row-cover flex-row justify-between">
<div class="label-cover">
<span class="text_41">*</span>
<span class="text_42">课程封面</span>
</div>
<div class="cover-upload-box flex-row">
<div class="image-text_6 flex-col justify-between" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">
<div class="native-upload">
<input
type="file"
id="coverUpload"
accept="image/*"
@change="handleNativeFileChange"
style="display: none;"
/>
<label for="coverUpload" class="upload-content" style="cursor: pointer; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
<template v-if="!previewUrl">
<div class="upload-plus-icon" style="font-size: 32px; display: block; text-align: center;">+</div>
<span class="upload-text" style="display: block; text-align: center;">上传</span>
</template>
<div v-if="previewUrl" style="position: relative; width: 100%; height: 100%;">
<img :src="previewUrl" style="max-width: 100%; max-height: 100%; object-fit: contain;" />
<div
class="delete-icon"
@click.stop="clearUpload"
style="position: absolute; top: 5px; right: 5px; background-color: rgba(0,0,0,0.5); color: white; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer;"
>
×
</div>
</div>
</label>
</div>
</div>
</div>
</div>
<!-- 课程简介标题和工具栏 -->
<div class="form-row-description flex-row">
<div class="label-description">
<span class="text_43">*</span>
<span class="text_44">课程简介</span>
</div>
</div>
<!-- 富文本编辑器区域 -->
<div class="editor-container flex-col">
<QuillEditor
v-model="formData.courseDescription"
:height="'280px'"
placeholder="请输入内容…"
class="rich-text-editor"
/>
</div>
<!-- 设置选项区域 -->
<div class="form-settings">
<!-- 离开页面停止播放 -->
<div class="form-row-settings flex-row">
<div class="settings-label flex-row justify-between">
<div class="label-settings">
<span class="text_47">*</span>
<span class="text_48">离开页面停止播放</span>
</div>
<button
@click="formData.stopOnLeave = !formData.stopOnLeave"
class="toggle-button"
:class="{ 'toggle-button-active': formData.stopOnLeave }"
>
{{ formData.stopOnLeave ? '开启' : '关闭' }}
</button>
</div>
</div>
<!-- 视频倍数播放 -->
<div class="form-row-setting1 flex-row">
<div class="setting1-label flex-row justify-between">
<div class="label-setting1">
<span class="text_49">*</span>
<span class="text_50">视频倍数播放</span>
</div>
<button
@click="formData.videoSpeedControl = !formData.videoSpeedControl"
class="toggle-button"
:class="{ 'toggle-button-active': formData.videoSpeedControl }"
>
{{ formData.videoSpeedControl ? '开启' : '关闭' }}
</button>
</div>
</div>
<!-- 显示视频文本 -->
<div class="form-row-setting2 flex-row">
<div class="setting2-label flex-row justify-between">
<div class="label-setting2">
<span class="text_51">*</span>
<span class="text_52">显示视频文本</span>
</div>
<button
@click="formData.showVideoText = !formData.showVideoText"
class="toggle-button"
:class="{ 'toggle-button-active': formData.showVideoText }"
>
{{ formData.showVideoText ? '开启' : '关闭' }}
</button>
</div>
</div>
<!-- 积分设置 -->
<div class="form-row-points flex-row">
<div class="points-label flex-row justify-between">
<div class="label-points">
<span class="text_53">*</span>
<span class="text_54">积分设置</span>
</div>
<button
@click="formData.pointsEnabled = !formData.pointsEnabled"
class="toggle-button"
:class="{ 'toggle-button-active': formData.pointsEnabled }"
>
{{ formData.pointsEnabled ? '开启' : '关闭' }}
</button>
</div>
<span class="text_55">学完可获得</span>
<div class="form-input-container">
<input
type="number"
v-model="formData.earnPoints"
min="0"
max="1000"
class="native-input"
/>
</div>
<span class="text_57">积分</span>
<span class="text_58">本课程需</span>
<div class="points-input-wrapper">
<input
type="number"
v-model="formData.requiredPoints"
min="0"
max="1000"
class="native-input"
/>
</div>
<span class="text_60">积分兑换</span>
</div>
</div>
<!-- 底部按钮区域 -->
<div class="form-actions flex-row">
<div class="action-button-group flex-col"></div>
<div class="btn-cancel flex-col" @click="handleCancel">
<span class="text_67">取消</span>
</div>
<div class="btn-submit flex-col" @click="handleSubmit">
<span class="text_68">保存</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
</template>
<script setup lang="ts">
import { ref, reactive, h } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
import { NInput, NSelect, NDatePicker } from 'naive-ui'
import QuillEditor from '@/components/common/QuillEditor.vue'
const router = useRouter()
const message = useMessage()
//
const formData = reactive({
courseName: '',
courseCategory: '',
instructors: [], //
sort: '',
startTime: null,
endTime: null,
studentType: 'all', // 'all' 'partial'
selectedClasses: [],
courseCover: '',
courseDescription: '',
//
allowDiscussion: true,
contentProtection: false,
allowTextContent: true,
studySettings: true,
//
stopOnLeave: true,
videoSpeedControl: false,
showVideoText: true,
//
pointsEnabled: true,
earnPoints: 60,
requiredPoints: 60,
//
studyDuration: 60,
studyProgress: 80,
totalLessons: 0,
studyDeadline: 0
})
//
const categoryOptions = [
{ label: '前端开发', value: 'frontend' },
{ label: '后端开发', value: 'backend' },
{ label: '移动开发', value: 'mobile' },
{ label: '人工智能', value: 'ai' },
{ label: '数据库', value: 'database' },
{ label: '运维部署', value: 'devops' },
{ label: '测试', value: 'testing' },
{ label: '产品设计', value: 'design' }
]
//
const instructorOptions = [
{ label: '李清林', value: '李清林' },
{ label: '刘树光', value: '刘树光' },
{ label: '肖蒙', value: '肖蒙' },
{ label: '张老师', value: '张老师' },
{ label: '王老师', value: '王老师' }
]
//
const classOptions = [
{ label: '前端开发班', value: 'frontend-class' },
{ label: '后端开发班', value: 'backend-class' },
{ label: 'AI算法班', value: 'ai-class' },
{ label: '全栈开发班', value: 'fullstack-class' }
]
//
const previewUrl = ref('')
//
const renderInstructorTag = ({ option, handleClose }: any) => {
return h('div', {
style: {
display: 'inline-flex',
alignItems: 'center',
padding: '2px 8px',
margin: '2px',
background: '#f0f0f0',
borderRadius: '4px',
fontSize: '14px'
}
}, [
option.label,
h('span', {
style: {
marginLeft: '4px',
cursor: 'pointer',
fontSize: '12px'
},
onClick: handleClose
}, '×')
])
}
//
const handleNativeFileChange = (event: Event) => {
const input = event.target as HTMLInputElement
if (input.files && input.files.length > 0) {
const file = input.files[0]
//
if (!file.type.startsWith('image/')) {
message.error('请上传图片文件')
return
}
// 2MB
if (file.size > 2 * 1024 * 1024) {
message.error('图片大小不能超过2MB')
return
}
// URL
previewUrl.value = URL.createObjectURL(file)
// //
// formData.courseCover = file
console.log('文件上传成功:', file.name)
}
}
//
const clearUpload = (event: Event) => {
event.preventDefault()
event.stopPropagation()
// URL
if (previewUrl.value) {
URL.revokeObjectURL(previewUrl.value)
}
//
previewUrl.value = ''
formData.courseCover = ''
//
const fileInput = document.getElementById('coverUpload') as HTMLInputElement
if (fileInput) {
fileInput.value = ''
}
}
//
const handleSubmit = async () => {
try {
//
if (!formData.courseName) {
message.error('请输入课程名称')
return
}
if (!formData.courseCategory) {
message.error('请选择课程分类')
return
}
if (formData.instructors.length === 0) {
message.error('请选择主讲老师')
return
}
if (!formData.courseCover) {
message.error('请上传课程封面')
return
}
// FormData
// 使
// const formDataToSubmit = new FormData()
// //
// Object.keys(formData).forEach(key => {
// if (key === 'courseCover' && formData[key] instanceof File) {
// formDataToSubmit.append('courseCover', formData[key])
// } else if (key === 'instructors' || key === 'selectedClasses') {
// //
// formData[key].forEach((item) => {
// formDataToSubmit.append(`${key}[]`, item)
// })
// } else {
// formDataToSubmit.append(key, String(formData[key]))
// }
// })
console.log('表单数据:', formData)
message.success('课程创建成功!')
// API
// const response = await api.createCourse(formDataToSubmit)
router.push('/teacher/course-management')
} catch (error) {
console.error('提交失败:', error)
message.error('提交失败,请重试')
}
}
//
const handleCancel = () => {
router.back()
}
</script>
<style scoped>
</style>
<style src="@/components/admin/CourseComponents/css/CourseCreate.css"></style>

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
<template>
<div class="course-management-container">
<!-- 左侧导航栏 -->
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item">
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item"
:class="{ active: $route.path === '/teacher/course-management' }">
<span>全部课程</span><i>(10)</i>
<div class="icon-container">
<span class="icon icon-status icon-add"></span>
@ -11,7 +12,8 @@
</div>
</router-link>
<div class="sub-nav-container">
<router-link to="/teacher/course-management/course-category" class="nav-item">
<router-link to="/teacher/course-management/course-category" class="nav-item sub-nav-item"
:class="{ active: $route.path === '/teacher/course-management/course-category' }">
<span>课程分类</span>
<div class="icon-container">
<span class="icon icon-status icon-add"></span>
@ -19,7 +21,8 @@
<span class="icon icon-status icon-delete"></span>
</div>
</router-link>
<router-link to="/teacher/course-management/material-category" class="nav-item">
<router-link to="/teacher/course-management/material-category" class="nav-item sub-nav-item"
:class="{ active: $route.path === '/teacher/course-management/material-category' }">
<span>资料分类</span>
<div class="icon-container">
<span class="icon icon-status icon-add"></span>
@ -27,69 +30,98 @@
<span class="icon icon-status icon-delete"></span>
</div>
</router-link>
<router-link to="/teacher/course-management/course-analysis" class="nav-item">
<span>课程分析</span>
<div class="icon-container">
<span class="icon icon-status icon-add"></span>
<span class="icon icon-status icon-edit"></span>
<span class="icon icon-status icon-delete"></span>
</div>
</router-link>
</div>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="content-container">
<router-view></router-view>
<!-- 全部课程页面直接显示课程分类组件 -->
<div v-if="$route.path === '/teacher/course-management'">
<CourseCategory />
</div>
<!-- 其他子路由内容 -->
<router-view v-else></router-view>
</div>
</div>
</template>
<script setup lang="ts">
import CourseCategory from './CourseComponents/CourseCategory.vue'
</script>
<style scoped>
.course-management-container{
.course-management-container {
display: flex;
}
.nav-container{
margin-top: 30px;
width: 266px;
.nav-container {
margin-top: 20px;
padding: 15px;
width: 274px;
background-color: #fff;
}
.sub-nav-container{
margin-left: 20px;
.sub-nav-container {
margin-left: 10px;
}
.nav-container .nav-item{
.nav-container .nav-item {
width: 240px;
height: 54px;
margin-left: 13px;
margin-top: 15px;
margin-top: 5px;
margin-bottom: 13px;
display: flex;
align-items: center;
padding: 0 15px;
border-radius: 8px;
background-color: #f5f7fa;
background-color: transparent;
color: #333;
text-decoration: none;
transition: all 0.3s ease;
font-size: 18px;
}
.nav-container .nav-item:hover{
.sub-nav-item {
width: 220px !important;
font-size: 16px !important;
}
.nav-container .nav-item:hover {
background-color: #e6f7ff;
}
.nav-container .nav-item span{
margin-right: 8px;
/* 选中状态样式 */
.nav-container .nav-item.active {
background-color: #F5F8FB;
color: #0288D1;
}
.nav-container .nav-item i{
.nav-container .nav-item.active span {
color: #0288D1;
}
.nav-container .nav-item.active i {
color: #0288D1;
}
.nav-container .nav-item span {
margin-right: 2px;
color: #666666;
}
.nav-container .nav-item i {
font-style: normal;
color: #66b7e3;
margin-right: 10px;
}
.icon-container{
.icon-container {
display: flex;
margin-left: auto;
}
.icon.icon-status{
.icon.icon-status {
width: 16px;
height: 16px;
background-size: contain;
@ -97,37 +129,60 @@
background-position: center;
display: inline-block;
margin-left: 8px;
opacity: 0;
opacity: 0.6;
transition: opacity 0.3s ease, background-image 0.3s ease;
}
/* 加号图标 */
.icon.icon-add {
background-image: url('/images/teacher/加号.png');
}
/* 编辑图标 */
.icon.icon-edit {
background-image: url('/images/teacher/编辑.png');
}
/* 删除图标 */
.icon.icon-delete {
background-image: url('/images/teacher/删除.png');
}
/* 鼠标悬停时各自切换为对应的选中图标 */
/* 鼠标悬停时显示有颜色的图标 */
.nav-item:hover .icon.icon-add {
background-image: url('/images/teacher/加号(选中).png');
opacity: 1;
}
.nav-item:hover .icon.icon-edit {
background-image: url('/images/teacher/编辑(选中).png');
opacity: 1;
}
.nav-item:hover .icon.icon-delete {
background-image: url('/images/teacher/删除(选中).png');
opacity: 1;
}
.content-container{
/* 选中状态下显示有颜色的图标 */
.nav-item.active .icon.icon-add {
background-image: url('/images/teacher/加号(选中).png');
opacity: 1;
}
.nav-item.active .icon.icon-edit {
background-image: url('/images/teacher/编辑(选中).png');
opacity: 1;
}
.nav-item.active .icon.icon-delete {
background-image: url('/images/teacher/删除(选中).png');
opacity: 1;
}
.content-container {
flex: 1;
padding: 20px;
padding: 20px 20px 20px 5px;
min-height: 100vh;
box-sizing: border-box;
}

View File

@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
// 导入页面组件
// ========== 前台页面组件 ==========
import Home from '@/views/Home.vue'
import Courses from '@/views/Courses.vue'
import CourseDetail from '@/views/CourseDetail.vue'
@ -26,317 +26,327 @@ import SpecialTraining from '@/views/SpecialTraining.vue'
import SpecialTrainingDetail from '@/views/SpecialTrainingDetail.vue'
import HelpCenter from '@/views/HelpCenter.vue'
// 管理员路由
// ========== 管理员后台组件 ==========
import AdminDashboard from '@/views/teacher/AdminDashboard.vue'
import PersonalCenter from '@/components/admin/PersonalCenter.vue'
import CourseManagement from '@/components/admin/CourseManagement.vue'
import MyResources from '@/components/admin/MyResources.vue'
import StudentManagement from '@/components/admin/StudentManagement.vue'
// 课程管理子路由组件
// 课程管理子组件
import CourseCategory from '@/components/admin/CourseComponents/CourseCategory.vue'
import MaterialCategory from '@/components/admin/CourseComponents/MaterialCategory.vue'
import CourseAnalysis from '@/components/admin/CourseComponents/CourseAnalysis.vue'
import CourseCreate from '@/components/admin/CourseComponents/CourseCreate.vue'
import CourseDetails from '@/components/admin/CourseComponents/CourseDetail.vue'
// 课程编辑子组件(新增)
import CourseEditor from '@/views/teacher/course/CourseEditor.vue'
import CoursewareManagement from '@/views/teacher/course/CoursewareManagement.vue'
import ChapterManagement from '@/views/teacher/course/ChapterManagement.vue'
import HomeworkManagement from '@/views/teacher/course/HomeworkManagement.vue'
import PracticeManagement from '@/views/teacher/course/PracticeManagement.vue'
import QuestionBankManagement from '@/views/teacher/course/QuestionBankManagement.vue'
import CertificateManagement from '@/views/teacher/course/CertificateManagement.vue'
import DiscussionManagement from '@/views/teacher/course/DiscussionManagement.vue'
import StatisticsManagement from '@/views/teacher/course/StatisticsManagement.vue'
import NotificationManagement from '@/views/teacher/course/NotificationManagement.vue'
import GeneralManagement from '@/views/teacher/course/GeneralManagement.vue'
// ========== 路由配置 ==========
const routes: RouteRecordRaw[] = [
// 管理后台路由
{
path: '/teacher',
name: 'AdminDashboard',
component: AdminDashboard,
meta: {
title: '管理后台',
requiresAuth: true
},
redirect: '/teacher/personal-center', // 添加默认重定向
redirect: '/teacher/course-management',
children: [
{
path: 'personal-center',
name: 'PersonalCenter',
component: PersonalCenter,
meta: {
title: '个人中心'
}
meta: { title: '个人中心' }
},
{
path: 'course-management',
name: 'CourseManagement',
component: CourseManagement,
meta: {
title: '课程管理'
},
redirect: '/teacher/course-management/course-category', // 添加默认重定向
meta: { title: '课程管理' },
children: [
{
path: 'course-category',
name: 'CourseCategory',
component: CourseCategory,
meta: {
title: '课程分类'
}
meta: { title: '课程分类' }
},
{
path: 'material-category',
name: 'MaterialCategory',
component: MaterialCategory,
meta: {
title: '资料分类'
}
meta: { title: '资料分类' }
},
{
path: 'course-analysis',
name: 'CourseAnalysis',
component: CourseAnalysis,
meta: {
title: '课程分析'
}
meta: { title: '课程分析' }
}
]
},
{
path: 'course-create',
name: 'CourseCreate',
component: CourseCreate,
meta: { title: '创建课程' }
},
{
path: 'course-editor/:id',
name: 'CourseEditor',
component: CourseEditor,
meta: { title: '编辑课程' },
redirect: (to) => `/teacher/course-editor/${to.params.id}/courseware`,
children: [
{
path: 'courseware',
name: 'CoursewareManagement',
component: CoursewareManagement,
meta: { title: '课件管理' }
},
{
path: 'chapters',
name: 'ChapterManagement',
component: ChapterManagement,
meta: { title: '章节管理' }
},
{
path: 'homework',
name: 'HomeworkManagement',
component: HomeworkManagement,
meta: { title: '作业管理' }
},
{
path: 'practice',
name: 'PracticeManagement',
component: PracticeManagement,
meta: { title: '练考通管理' }
},
{
path: 'question-bank',
name: 'QuestionBankManagement',
component: QuestionBankManagement,
meta: { title: '题库管理' }
},
{
path: 'certificate',
name: 'CertificateManagement',
component: CertificateManagement,
meta: { title: '证书管理' }
},
{
path: 'discussion',
name: 'DiscussionManagement',
component: DiscussionManagement,
meta: { title: '讨论管理' }
},
{
path: 'statistics',
name: 'StatisticsManagement',
component: StatisticsManagement,
meta: { title: '统计管理' }
},
{
path: 'notification',
name: 'NotificationManagement',
component: NotificationManagement,
meta: { title: '通知管理' }
},
{
path: 'management',
name: 'GeneralManagement',
component: GeneralManagement,
meta: { title: '综合管理' }
}
]
},
{
path: 'my-resources',
name: 'MyResources',
component: MyResources,
meta: {
title: '我的资源'
}
meta: { title: '我的资源' }
},
{
path: 'student-management',
name: 'StudentManagement',
component: StudentManagement,
meta: {
title: '学员管理'
}
},
{
path: 'course-create',
name: 'CourseCreate',
component: CourseCreate,
meta: {
title: '创建课程'
}
},
{
path: 'course-detail',
name: 'TeacherCourseDetail',
component: CourseDetails,
meta: {
title: '课程详情'
}
meta: { title: '学员管理' }
}
]
},
// 帮助中心
{
path: '/help-center',
name: 'HelpCenter',
component: HelpCenter,
meta: {
title: '帮助中心'
}
meta: { title: '帮助中心' }
},
// 首页与课程
{
path: '/',
name: 'Home',
component: Home,
meta: {
title: '首页'
}
meta: { title: '首页' }
},
{
path: '/courses',
name: 'Courses',
component: Courses,
meta: {
title: '课程列表'
}
meta: { title: '课程列表' }
},
{
path: '/course/:id',
name: 'CourseDetail',
component: CourseDetail,
meta: {
title: '课程详情'
}
meta: { title: '课程详情' }
},
{
path: '/course/:id/enrolled',
name: 'CourseDetailEnrolled',
component: CourseDetailEnrolled,
meta: {
title: '课程详情 - 已报名'
}
meta: { title: '课程详情 - 已报名' }
},
{
path: '/course/study/:id',
name: 'CourseStudy',
component: CourseStudy,
meta: {
title: '课程学习',
requiresAuth: true
}
meta: { title: '课程学习', requiresAuth: true }
},
{
path: '/learning/:id',
name: 'Learning',
component: Learning,
meta: {
title: '学习中心',
requiresAuth: true
}
meta: { title: '学习中心', requiresAuth: true }
},
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: {
title: '个人中心',
requiresAuth: true
}
meta: { title: '个人中心', requiresAuth: true }
},
// 其他功能页面
{
path: '/learning-paths',
name: 'LearningPaths',
component: LearningPaths,
meta: {
title: '学习路径'
}
meta: { title: '学习路径' }
},
{
path: '/faculty',
name: 'Faculty',
component: Faculty,
meta: {
title: '师资力量'
}
meta: { title: '师资力量' }
},
{
path: '/teacher/:id',
name: 'TeacherDetail',
component: TeacherDetail,
meta: {
title: '讲师详情'
}
meta: { title: '讲师详情' }
},
{
path: '/resources',
name: 'Resources',
component: Resources,
meta: {
title: '精选资源'
}
meta: { title: '精选资源' }
},
{
path: '/special-training',
name: 'SpecialTraining',
component: SpecialTraining,
meta: {
title: '专题训练'
}
meta: { title: '专题训练' }
},
{
path: '/special-training/:id',
name: 'SpecialTrainingDetail',
component: SpecialTrainingDetail,
meta: {
title: '专题训练详情'
}
meta: { title: '专题训练详情' }
},
{
path: '/activities',
name: 'Activities',
component: Activities,
meta: {
title: '全部活动'
}
meta: { title: '全部活动' }
},
{
path: '/activity/:id',
name: 'ActivityDetail',
component: ActivityDetail,
meta: {
title: '活动详情'
}
meta: { title: '活动详情' }
},
{
path: '/activity/:id/register',
name: 'ActivityRegistration',
component: ActivityRegistration,
meta: {
title: '活动报名'
}
meta: { title: '活动报名' }
},
{
path: '/course/:courseId/exam/:sectionId/notice',
name: 'ExamNotice',
component: ExamNotice,
meta: {
title: '考前须知'
}
meta: { title: '考前须知' }
},
{
path: '/course/:courseId/exam/:sectionId',
name: 'Exam',
component: Exam,
meta: {
title: '在线考试'
}
meta: { title: '在线考试' }
},
{
path: '/exam/submitted',
name: 'ExamSubmitted',
component: ExamSubmitted,
meta: {
title: '考试提交成功'
}
meta: { title: '考试提交成功' }
},
{
path: '/ai',
name: 'AI',
component: () => import('@/views/Ai.vue'),
meta: {
title: 'AI'
}
meta: { title: 'AI' }
},
{
path: '/ai-demo',
name: 'AIDemo',
component: () => import('@/views/DetailView.vue'),
meta: {
title: 'AI演示'
}
meta: { title: 'AI演示' }
},
{
path: '/test-sections',
name: 'TestSections',
component: TestSections,
meta: {
title: '测试章节API'
}
meta: { title: '测试章节API' }
},
{
path: '/local-video-demo',
name: 'LocalVideoDemo',
component: LocalVideoDemo,
meta: {
title: '本地视频播放演示'
}
meta: { title: '本地视频播放演示' }
},
// 404 路由
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue'),
meta: {
title: '页面未找到'
},
meta: { title: '页面未找到' }
}
]
// ========== 创建路由实例 ==========
const router = createRouter({
history: createWebHistory(),
routes,
@ -349,7 +359,7 @@ const router = createRouter({
}
})
// 路由守卫
// ========== 路由守卫 ==========
router.beforeEach((to, _from, next) => {
// 设置页面标题
if (to.meta.title) {
@ -358,10 +368,8 @@ router.beforeEach((to, _from, next) => {
// 检查是否需要登录
if (to.meta.requiresAuth) {
// 检查用户登录状态
const token = localStorage.getItem('token')
if (!token) {
// 未登录时跳转到首页,用户可以通过模态框登录
next('/')
return
}
@ -370,4 +378,4 @@ router.beforeEach((to, _from, next) => {
next()
})
export default router
export default router

View File

@ -1,55 +1,59 @@
<template>
<div class="admin-dashboard">
<!-- 顶部图片 -->
<div class="top-image-container">
<img src="/images/teacher/顶部.png" alt="顶部图片" class="top-image">
<div class="top-image-container">
<img src="/images/teacher/顶部.png" alt="顶部图片" class="top-image">
</div>
</div>
<div class="main-content">
<!-- 侧边栏 -->
<div class="sidebar-container">
<!-- 头像 -->
<div class="avatar-container">
<img src="https://picsum.photos/200/200" alt="头像" class="avatar">
<div class="avatar-text">
用户昵称~
</div>
</div>
<!-- 头像 -->
<div class="avatar-container">
<img src="https://picsum.photos/200/200" alt="头像" class="avatar">
<div class="avatar-text">
用户昵称~
</div>
</div>
<!-- 导航栏 -->
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item" :class="{ active: activeNavItem === 0 }" @click="setActiveNavItem(0)">
<img :src="activeNavItem === 0 ? '/images/teacher/课程管理(选中).png' : '/images/teacher/课程管理.png'" alt="">
<span>课程管理</span>
</router-link>
<router-link to="/teacher/student-management" class="nav-item" :class="{ active: activeNavItem === 1 }" @click="setActiveNavItem(1)">
<!-- 导航栏 -->
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item" :class="{ active: activeNavItem === 0 }"
@click="setActiveNavItem(0)">
<img :src="activeNavItem === 0 ? '/images/teacher/课程管理(选中).png' : '/images/teacher/课程管理.png'" alt="">
<span>课程管理</span>
</router-link>
<router-link to="/teacher/student-management" class="nav-item" :class="{ active: activeNavItem === 1 }"
@click="setActiveNavItem(1)">
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt="">
<span>学员管理</span>
</router-link>
<router-link to="/teacher/my-resources" class="nav-item" :class="{ active: activeNavItem === 2 }" @click="setActiveNavItem(2)">
<img :src="activeNavItem === 2 ? '/images/teacher/我的资源(选中).png' : '/images/teacher/我的资源.png'" alt="">
<span>我的资源</span>
</router-link>
<router-link to="/teacher/personal-center" class="nav-item" :class="{ active: activeNavItem === 3 }" @click="setActiveNavItem(3)">
<img :src="activeNavItem === 1 ? '/images/teacher/学院管理(选中).png' : '/images/teacher/学员管理.png'" alt="">
<span>学员管理</span>
</router-link>
<router-link to="/teacher/my-resources" class="nav-item" :class="{ active: activeNavItem === 2 }"
@click="setActiveNavItem(2)">
<img :src="activeNavItem === 2 ? '/images/teacher/我的资源(选中).png' : '/images/teacher/我的资源.png'" alt="">
<span>我的资源</span>
</router-link>
<router-link to="/teacher/personal-center" class="nav-item" :class="{ active: activeNavItem === 3 }"
@click="setActiveNavItem(3)">
<img :src="activeNavItem === 3 ? '/images/teacher/个人中心(选中).png' : '/images/teacher/个人中心.png'" alt="">
<span>个人中心</span>
</router-link>
</div>
<img :src="activeNavItem === 3 ? '/images/teacher/个人中心(选中).png' : '/images/teacher/个人中心.png'" alt="">
<span>个人中心</span>
</router-link>
</div>
</div>
<!-- 右侧路由视图 -->
<div class="router-view-container">
<!-- 面包屑 -->
<div class="breadcrumb">
<span class="breadcrumb-separator">|</span>
<n-breadcrumb>
<n-breadcrumb-item v-for="(item, index) in breadcrumbItems" :key="index" :to="item.path">
{{ item.title }}
</n-breadcrumb-item>
</n-breadcrumb>
<span class="breadcrumb-separator"></span>
<n-breadcrumb>
<n-breadcrumb-item v-for="(item, index) in breadcrumbItems" :key="index" :to="item.path">
{{ item.title }}
</n-breadcrumb-item>
</n-breadcrumb>
</div>
<router-view></router-view>
</div>
@ -67,7 +71,7 @@ const height = window.innerHeight;
console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`);
//
const activeNavItem = ref(0);
const activeNavItem = ref(0); // 0: , 1: , 2: , 3:
const route = useRoute();
const setActiveNavItem = (index: number) => {
@ -78,7 +82,7 @@ const setActiveNavItem = (index: number) => {
const breadcrumbItems = computed(() => {
// matched
const matchedRoutes = route.matched;
// matchedRoutes''
return matchedRoutes
.filter(item => item.meta.title !== '管理后台')
@ -103,13 +107,13 @@ watch(route, () => {
const updateActiveNavItem = () => {
const path = route.path;
if (path.includes('course-management')) {
activeNavItem.value = 0;
activeNavItem.value = 0; //
} else if (path.includes('student-management')) {
activeNavItem.value = 1;
activeNavItem.value = 1; //
} else if (path.includes('my-resources')) {
activeNavItem.value = 2;
activeNavItem.value = 2; //
} else if (path.includes('personal-center')) {
activeNavItem.value = 3;
activeNavItem.value = 3; //
}
}
@ -137,109 +141,121 @@ const updateActiveNavItem = () => {
display: flex;
}
.sidebar-container{
width: 294px;
height: calc(100vh - 130px);
background: #FFFFFF;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
.sidebar-container {
width: 294px;
height: calc(100vh - 130px);
background: #FFFFFF;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.avatar-container{
height: 230px;
position: relative;
/* 移除原来的边框 */
.avatar-container {
height: 230px;
position: relative;
/* 移除原来的边框 */
}
/* 添加带间距的横线 */
.avatar-container::after {
content: '';
position: absolute;
bottom: 0;
left: 20px;
right: 20px;
height: 1px;
background-color: #E6E6E6;
content: '';
position: absolute;
bottom: 0;
left: 20px;
right: 20px;
height: 1px;
background-color: #E6E6E6;
}
.avatar-container img{
width: 95px;
height: 95px;
/* 圆角 */
border-radius: 50%;
margin-top: 55px;
margin-left: 100px;
margin-right: 99;
.avatar-container img {
width: 95px;
height: 95px;
/* 圆角 */
border-radius: 50%;
margin-top: 55px;
margin-left: 100px;
margin-right: 99;
}
.avatar-text{
margin-left: 98px;
height: 31px;
font-family: AppleSystemUIFont;
font-size: 22px;
color: #000000;
line-height: 26px;
text-align: left;
font-style: normal;
text-transform: none;
.avatar-text {
margin-left: 98px;
height: 31px;
font-family: AppleSystemUIFont;
font-size: 22px;
color: #000000;
line-height: 26px;
text-align: left;
font-style: normal;
text-transform: none;
}
.nav-container{
margin-top: 30px;
/* 鼠标变小手 */
cursor: pointer;
.nav-container {
margin-top: 30px;
/* 鼠标变小手 */
cursor: pointer;
}
.nav-container .nav-item{
margin-left: 20px;
width: 254px;
height: 54px;
margin-bottom: 20px;
/* 圆角 */
border-radius: 10px;
display: flex;
align-items: center;
transition: all 0.3s ease;
.nav-container .nav-item {
margin-left: 20px;
width: 254px;
height: 54px;
margin-bottom: 20px;
/* 圆角 */
border-radius: 10px;
display: flex;
align-items: center;
transition: all 0.3s ease;
}
.nav-container .nav-item:hover {
background: rgba(102,183,227,0.05);
background: rgba(102, 183, 227, 0.05);
}
/* 添加激活状态样式 */
.nav-container .nav-item.active{
background: rgba(102,183,227,0.1);
.nav-container .nav-item.active {
background: rgba(102, 183, 227, 0.1);
}
.nav-container .nav-item img{
width: 17px;
height: 20px;
margin-left: 50px;
margin-top: 0;
margin-right: 5px;
.nav-container .nav-item.active span {
color: #0C99DA;
}
.nav-container .nav-item span{
width: 80px;
height: 28px;
font-family: AppleSystemUIFont;
font-size: 20px;
color: #0C99DA;
line-height: 23px;
text-align: left;
font-style: normal;
text-transform: none;
.nav-container .nav-item img {
width: 17px;
height: 20px;
margin-left: 50px;
margin-top: 0;
margin-right: 5px;
}
.nav-container .nav-item span {
width: 80px;
height: 28px;
font-family: AppleSystemUIFont;
font-size: 20px;
color: #666666;
line-height: 23px;
text-align: left;
font-style: normal;
text-transform: none;
}
.router-view-container {
flex: 1;
padding: 20px;
background: #F5F7FA;
height: calc(100vh - 130px);
overflow-y: auto;
flex: 1;
padding: 20px;
background: #F5F7FA;
height: calc(100vh - 130px);
overflow-y: auto;
}
.breadcrumb {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.breadcrumb-separator {
margin: 0 8px;
color: #0C99DA;
font-size: 16px;
content: '>';
width: 4px;
height: 17px;
margin-right: 10px;
background-color: #0C99DA;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="certificate-management">
<div class="content-placeholder">
<h2>证书管理</h2>
<p>证书管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.certificate-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="chapter-management">
<div class="content-placeholder">
<h2>章节管理</h2>
<p>章节管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.chapter-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,197 @@
<template>
<div class="course-editor">
<!-- 左侧导航菜单 -->
<div class="sidebar">
<router-link
:to="`/teacher/course-editor/${courseId}/courseware`"
class="menu-item"
:class="{ active: $route.path.includes('courseware') }"
>
<img
:src="$route.path.includes('courseware') ? '/images/teacher/课件-选中.png' : '/images/teacher/课件.png'"
alt="课件"
/>
<span>课件</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/chapters`"
class="menu-item"
:class="{ active: $route.path.includes('chapters') }"
>
<img
:src="$route.path.includes('chapters') ? '/images/teacher/章节-选中.png' : '/images/teacher/章节.png'"
alt="章节"
/>
<span>章节</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/homework`"
class="menu-item"
:class="{ active: $route.path.includes('homework') }"
>
<img
:src="$route.path.includes('homework') ? '/images/teacher/作业-选中.png' : '/images/teacher/作业.png'"
alt="作业"
/>
<span>作业</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/practice`"
class="menu-item"
:class="{ active: $route.path.includes('practice') }"
>
<img
:src="$route.path.includes('practice') ? '/images/teacher/练考通-选中.png' : '/images/teacher/练考通.png'"
alt="练考通"
/>
<span>练考通</span>
</router-link>
<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"
:class="{ active: $route.path.includes('certificate') }"
>
<img
:src="$route.path.includes('certificate') ? '/images/teacher/证书-选中.png' : '/images/teacher/证书.png'"
alt="证书"
/>
<span>证书</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/discussion`"
class="menu-item"
:class="{ active: $route.path.includes('discussion') }"
>
<img
:src="$route.path.includes('discussion') ? '/images/teacher/讨论-选中.png' : '/images/teacher/讨论.png'"
alt="讨论"
/>
<span>讨论</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/statistics`"
class="menu-item"
:class="{ active: $route.path.includes('statistics') }"
>
<img
:src="$route.path.includes('statistics') ? '/images/teacher/统计-选中.png' : '/images/teacher/统计.png'"
alt="统计"
/>
<span>统计</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/notification`"
class="menu-item"
:class="{ active: $route.path.includes('notification') }"
>
<img
:src="$route.path.includes('notification') ? '/images/teacher/通知-选中.png' : '/images/teacher/通知.png'"
alt="通知"
/>
<span>通知</span>
</router-link>
<router-link
:to="`/teacher/course-editor/${courseId}/management`"
class="menu-item"
:class="{ active: $route.path.includes('management') }"
>
<img
:src="$route.path.includes('management') ? '/images/teacher/管理-选中.png' : '/images/teacher/管理.png'"
alt="管理"
/>
<span>管理</span>
</router-link>
</div>
<!-- 右侧内容区域 -->
<div class="content-area">
<router-view />
</div>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
// ID
const courseId = route.params.id
</script>
<style scoped>
.course-editor {
display: flex;
margin-top: 5px;
height: 100vh;
background: #f5f5f5;
}
/* 左侧导航 */
.sidebar {
width: 273px;
background: #fff;
border-right: 1px solid #e8e8e8;
padding: 20px 15px;
margin-right: 5px;
}
.menu-item {
display: flex;
align-items: center;
padding: 15px 20px;
margin-bottom: 5px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 3px solid transparent;
text-decoration: none;
color: inherit;
font-size: 18px;
color: #666;
border-radius: 5px;
}
.menu-item:hover {
background: #f5f5f5;
}
.menu-item.active {
background: #F5F8FB;
color: #0288D1;
}
.menu-item.active span {
color: #0288D1;
}
.menu-item img {
margin-left: 40px;
margin-top: 1px;
width: 16px;
height: 16px;
margin-right: 10px;
}
.menu-item span {
font-size: 16px;
color: #666;
}
/* 右侧内容区域 */
.content-area {
flex: 1;
background-color: #fff;
overflow: auto;
}
</style>

View File

@ -0,0 +1,870 @@
<template>
<div class="courseware-management">
<!-- 顶部操作栏 -->
<div class="toolbar">
<h2>全部课件</h2>
<div class="toolbar-actions">
<button class="btn btn-primary" @click="addCourseware">添加课件</button>
<button class="btn btn-new" @click="createFolder">新建文件夹</button>
<button class="btn btn-default" @click="moveFiles" :disabled="selectedFiles.length === 0">移动</button>
<button class="btn btn-danger" @click="deleteSelected" :disabled="selectedFiles.length === 0">删除</button>
<div class="search-box">
<input type="text" placeholder="请输入想要搜索的内容" v-model="searchKeyword" />
<button class="btn btn-search" @click="searchFiles">搜索</button>
</div>
</div>
</div>
<!-- 文件列表表格 -->
<div class="table-box">
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
<n-data-table :columns="columns" :data="paginatedFiles" :row-key="rowKey" :checked-row-keys="selectedFiles"
@update:checked-row-keys="handleCheck" :bordered="false" :single-line="false" size="medium"
class="file-data-table" :row-class-name="rowClassName" />
</n-config-provider>
<!-- 自定义分页器 -->
<div class="custom-pagination">
<div class="pagination-content">
<div class="page-numbers">
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('first')">
首页
</span>
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('prev')">
上一页
</span>
<span v-for="page in visiblePages" :key="page" class="page-number page-number-bordered"
:class="{ active: page === currentPage }" @click="goToPage(page)">
{{ page }}
</span>
<span v-if="showRightEllipsis" class="page-number">...</span>
<span v-if="totalPages > 1" class="page-number page-number-bordered" @click="goToPage(totalPages)">
{{ totalPages }}
</span>
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
@click="goToPage('next')">
下一页
</span>
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
@click="goToPage('last')">
尾页
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, h } from 'vue'
import { NButton, NDropdown, NTag, useMessage, NDataTable, NConfigProvider, zhCN, dateZhCN } from 'naive-ui'
import type { DataTableColumns, DropdownOption } from 'naive-ui'
const message = useMessage()
//
interface FileItem {
id: number
name: string
type: string
size: string
creator: string
createTime: string
isTop: boolean
}
//
const searchKeyword = ref('')
//
const selectedFiles = ref<number[]>([])
//
const fileList = ref<FileItem[]>([
{
id: 1,
name: '文件名称文件名称文件名称',
type: 'folder',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: true
},
{
id: 2,
name: '这是一个表格文件.xlsx',
type: 'excel',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: false
},
{
id: 3,
name: '这是一个表格文件.xlsx',
type: 'excel',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: false
},
{
id: 4,
name: '这是一个表格文件.xlsx',
type: 'word',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: false
},
{
id: 5,
name: '这是一个表格文件.xlsx',
type: 'pdf',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: false
},
{
id: 6,
name: '这是一个表格文件.xlsx',
type: 'video',
size: '1MB',
creator: '王建国',
createTime: '2025.07.25 09:20',
isTop: false
}
])
//
const filteredFiles = computed(() => {
if (!searchKeyword.value) {
return fileList.value
}
return fileList.value.filter((file: FileItem) =>
file.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
//
const getFileIcon = (type: string) => {
const iconMap: { [key: string]: string } = {
folder: '/images/activity/file.png',
excel: '/images/activity/xls.png',
word: '/images/activity/wrod.png',
pdf: '/images/activity/pdf.png',
ppt: '/images/activity/ppt.png',
video: '/images/activity/file.png'
}
return iconMap[type] || '/images/activity/file.png'
}
//
const rowKey = (row: FileItem) => row.id
//
const handleCheck = (rowKeys: number[]) => {
selectedFiles.value = rowKeys
}
//
const rowClassName = () => {
return 'file-table-row'
}
//
const currentPage = ref(1)
const pageSize = ref(10)
const totalPages = computed(() => Math.ceil(filteredFiles.value.length / pageSize.value))
//
const visiblePages = computed(() => {
const pages = []
const current = currentPage.value
const total = totalPages.value
if (total <= 7) {
// 7
for (let i = 1; i <= total; i++) {
pages.push(i)
}
} else {
//
const start = Math.max(1, current - 2)
const end = Math.min(total, current + 2)
for (let i = start; i <= end; i++) {
pages.push(i)
}
}
return pages
})
//
const showRightEllipsis = computed(() => {
return currentPage.value < totalPages.value - 3
})
//
const goToPage = (page: string | number) => {
if (typeof page === 'string') {
switch (page) {
case 'first':
currentPage.value = 1
break
case 'prev':
if (currentPage.value > 1) currentPage.value--
break
case 'next':
if (currentPage.value < totalPages.value) currentPage.value++
break
case 'last':
currentPage.value = totalPages.value
break
}
} else {
currentPage.value = page
}
}
//
const paginatedFiles = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredFiles.value.slice(start, end)
})
//
const getMoreOptions = (row: FileItem): DropdownOption[] => [
{
label: '重命名',
key: 'rename',
icon: () => h('img', {
src: '/images/teacher/重命名.png',
alt: '重命名',
style: { width: '16px', height: '16px' }
})
},
{
label: row.isTop ? '取消置顶' : '置顶',
key: 'toggle-top',
icon: () => h('img', {
src: '/images/teacher/置顶.png',
alt: '置顶',
style: { width: '16px', height: '16px' }
})
},
{
label: '权限设置',
key: 'permissions',
icon: () => h('img', {
src: '/images/teacher/权限设置.png',
alt: '权限设置',
style: { width: '16px', height: '16px' }
})
},
{
label: '下载',
key: 'download',
icon: () => h('img', {
src: '/images/teacher/下载.png',
alt: '下载',
style: { width: '16px', height: '16px' }
})
}
]
//
const handleMoreAction = (key: string, row: FileItem) => {
switch (key) {
case 'rename':
renameFile(row)
break
case 'toggle-top':
toggleTop(row)
break
case 'permissions':
setPermissions(row)
break
case 'download':
downloadFile(row)
break
}
}
//
const columns: DataTableColumns<FileItem> = [
{
type: 'selection'
},
{
title: '序号',
key: 'index',
width: 80,
render: (_row: FileItem, index: number) => index + 1
},
{
title: '名称',
key: 'name',
minWidth: 200,
render: (row: FileItem) => {
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
h('img', {
src: getFileIcon(row.type),
alt: row.type,
style: { width: '16px', height: '16px' }
}),
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
h('span', { style: { color: '#062333' } }, row.name),
row.isTop ? h(NTag, { type: 'info', size: 'small' }, { default: () => '置顶' }) : null
])
])
}
},
{
title: '大小',
key: 'size',
width: 100
},
{
title: '创建人',
key: 'creator',
width: 120
},
{
title: '创建时间',
key: 'createTime',
width: 180
},
{
title: '操作',
key: 'actions',
width: 320,
render: (row: FileItem) => {
return h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' } }, [
h(NButton, {
size: 'small',
type: 'info',
secondary: true,
onClick: () => uploadFile(row)
}, { default: () => '上传文件' }),
h(NButton, {
size: 'small',
type: 'primary',
secondary: true,
onClick: () => viewFile(row)
}, { default: () => '查看' }),
h(NButton, {
size: 'small',
type: 'error',
secondary: true,
onClick: () => deleteFile(row)
}, { default: () => '删除' }),
h(NDropdown, {
options: getMoreOptions(row),
onSelect: (key: string) => handleMoreAction(key, row)
}, {
default: () => h(NButton, { size: 'small', secondary: true }, { default: () => '更多' })
})
])
}
}
]
//
const addCourseware = () => {
message.info('添加课件功能')
}
const createFolder = () => {
message.info('新建文件夹功能')
}
const moveFiles = () => {
if (selectedFiles.value.length === 0) return
message.info(`移动 ${selectedFiles.value.length} 个文件`)
}
const deleteSelected = () => {
if (selectedFiles.value.length === 0) return
if (confirm(`确定要删除选中的 ${selectedFiles.value.length} 个文件吗?`)) {
selectedFiles.value.forEach((id: number) => {
const index = fileList.value.findIndex((f: FileItem) => f.id === id)
if (index > -1) {
fileList.value.splice(index, 1)
}
})
selectedFiles.value = []
message.success('删除成功')
}
}
const searchFiles = () => {
message.info('搜索文件: ' + searchKeyword.value)
}
const uploadFile = (file: FileItem) => {
message.info('上传文件: ' + file.name)
}
const viewFile = (file: FileItem) => {
message.info('查看文件: ' + file.name)
}
const deleteFile = (file: FileItem) => {
if (confirm('确定要删除这个文件吗?')) {
const index = fileList.value.findIndex((f: FileItem) => f.id === file.id)
if (index > -1) {
fileList.value.splice(index, 1)
message.success('删除成功')
}
}
}
const renameFile = (file: FileItem) => {
const newName = prompt('请输入新的文件名:', file.name)
if (newName && newName !== file.name) {
file.name = newName
message.success('重命名成功')
}
}
const toggleTop = (file: FileItem) => {
file.isTop = !file.isTop
message.success(file.isTop ? '已置顶' : '已取消置顶')
}
const setPermissions = (file: FileItem) => {
message.info('权限设置: ' + file.name)
}
const downloadFile = (file: FileItem) => {
message.info('下载文件: ' + file.name)
}
</script>
<style scoped>
.courseware-management {
width: 1293px;
background: #fff;
height: 100%;
overflow: auto;
}
/* 顶部工具栏 */
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-right: 25px;
background: #fff;
padding: 30px 0 20px 30px;
border-bottom: 2px solid #F6F6F6;
}
.toolbar h2 {
margin: 0;
font-size: 18px;
color: #333;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 10px;
}
.btn {
padding: 7px 16px;
border: none;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 4px;
}
.btn-primary {
background: #0288D1;
color: white;
}
.btn-primary:hover {
background: #40a9ff;
}
.btn-new {
background-color: #fff;
border: 1px solid #0288D1;
color: #0288D1;
}
.btn-default {
background: white;
color: #999999;
border: 1px solid #999999;
}
.btn-default:hover {
border-color: #1890ff;
color: #1890ff;
}
.btn-danger {
background: white;
color: #FF4D4F;
border: 1px solid #FF4D4F;
}
.btn-danger:hover {
background: #ff7875;
}
.btn-default:disabled,
.btn-danger:disabled {
/* opacity: 0.5; */
/* cursor: not-allowed; */
}
.search-box {
display: flex;
align-items: center;
border: 1px solid #d9d9d9;
border-radius: 4px;
overflow: hidden;
}
.search-box input {
border: none;
padding: 6px 12px;
outline: none;
width: 200px;
font-size: 14px;
}
.btn-search {
background: #0288D1;
color: white;
border: none;
padding: 6px 16px;
cursor: pointer;
font-size: 16px;
}
.btn-search:hover {
background: #0277bd;
}
.table-box {
display: flex;
flex-direction: column;
height: 1181px;
justify-content: space-between;
}
/* Naive UI 表格样式定制 */
:deep(.file-data-table) {
background: #fff;
border-radius: 8px;
padding: 40px;
}
/* 表格头部样式 */
:deep(.file-data-table .n-data-table-thead) {
background: #fafafa;
}
:deep(.file-data-table .n-data-table-th) {
background: #fafafa;
font-weight: 500;
color: #062333;
font-size: 14px;
border-bottom: 1px solid #e8e8e8;
padding: 12px 8px;
text-align: center;
}
/* 表格行样式 */
:deep(.file-data-table .n-data-table-td) {
font-size: 13px;
color: #062333;
border-bottom: 1px solid #f0f0f0;
padding: 12px 8px;
vertical-align: middle;
text-align: center;
}
/* 名称列左对齐 */
:deep(.file-data-table .n-data-table-td[data-col-key="name"]) {
text-align: left;
}
:deep(.file-data-table .n-data-table-th[data-col-key="name"]) {
text-align: left;
}
:deep(.file-data-table .n-data-table-tr:hover) {
background: #fafafa;
}
/* 复选框样式 */
:deep(.file-data-table .n-checkbox) {
--n-size: 16px;
}
/* 按钮组样式调整 */
:deep(.file-data-table .n-button) {
font-size: 12px;
height: 28px;
padding: 0 12px;
margin: 2px;
}
/* 下拉菜单样式 */
:deep(.n-dropdown-option) {
font-size: 12px;
padding: 8px 12px;
}
/* 分页样式 */
:deep(.n-data-table__pagination) {
position: relative;
padding: 20px 0;
margin-top: 20px;
background: #fff;
}
:deep(.n-pagination) {
justify-content: center;
margin: 0;
}
/* 当前页面颜色调整 */
:deep(.n-pagination-item--active) {
background-color: #0088D1 !important;
border-color: #0088D1 !important;
color: #fff !important;
}
:deep(.n-pagination-item--active:hover) {
background-color: #0077B8 !important;
border-color: #0077B8 !important;
}
/* 分页按钮悬停效果 */
:deep(.n-pagination-item:hover:not(.n-pagination-item--disabled):not(.n-pagination-item--active)) {
background-color: #f5f5f5;
border-color: #0088D1;
color: #0088D1;
}
/* 快速跳转输入框样式 */
:deep(.n-pagination-quick-jumper .n-input) {
width: 50px;
}
/* 页面大小选择器样式 */
:deep(.n-select .n-base-selection) {
border-color: #e0e0e0;
}
:deep(.n-select .n-base-selection:hover) {
border-color: #0088D1;
}
/* 标签样式 */
:deep(.n-tag) {
font-size: 10px;
height: 18px;
line-height: 16px;
padding: 0 6px;
}
/* 输入框样式 */
:deep(.n-input) {
font-size: 14px;
}
/* 表格行样式 */
:deep(.file-table-row) {
transition: background-color 0.2s;
}
:deep(.file-table-row:hover) {
background-color: #f8f9fa;
}
/* 操作按钮容器 */
:deep(.file-data-table .n-data-table-td:last-child) {
padding: 8px;
}
:deep(.file-data-table .n-data-table-table) {
border-spacing: 0;
}
:deep(.file-data-table .n-data-table-th:first-child),
:deep(.file-data-table .n-data-table-td:first-child) {
padding-left: 16px;
}
:deep(.file-data-table .n-data-table-th:last-child),
:deep(.file-data-table .n-data-table-td:last-child) {
padding-right: 16px;
}
/* 表格边框 */
:deep(.file-data-table .n-data-table-table) {
border: 1px solid #f0f0f0;
border-radius: 8px;
}
/* 操作按钮样式 - 只有边框和文字颜色,无背景色 */
:deep(.file-data-table .n-button--info-type.n-button--secondary) {
background-color: transparent !important;
border: 1px solid #0288D1;
color: #0288D1;
}
:deep(.file-data-table .n-button--info-type.n-button--secondary:hover) {
background-color: rgba(32, 128, 240, 0.05) !important;
border: 1px solid #0288D1;
color: #248DD3;
}
:deep(.file-data-table .n-button--primary-type.n-button--secondary) {
background-color: transparent !important;
border: 1px solid #0288D1;
color: #0288D1;
}
:deep(.file-data-table .n-button--primary-type.n-button--secondary:hover) {
background-color: rgba(24, 160, 88, 0.05) !important;
border: 1px solid #0288D1;
color: #0288D1;
}
:deep(.file-data-table .n-button--error-type.n-button--secondary) {
background-color: transparent !important;
border: 1px solid #FF4D4F;
color: #FD8485;
}
:deep(.file-data-table .n-button--error-type.n-button--secondary:hover) {
background-color: rgba(208, 48, 80, 0.05) !important;
border: 1px solid #FF4D4F;
color: #FD8485;
}
:deep(.file-data-table .n-button--default-type.n-button--secondary) {
background-color: transparent !important;
border: 1px solid #4165D7;
color: #4165D7;
}
:deep(.file-data-table .n-button--default-type.n-button--secondary:hover) {
background-color: rgba(0, 0, 0, 0.02) !important;
border: 1px solid #4165D7;
color: #4165D7;
}
/* 下拉菜单图标悬停效果 */
:deep(.n-dropdown-option:hover img[alt="重命名"]) {
content: url('/images/teacher/重命名-选中.png');
}
:deep(.n-dropdown-option:hover img[alt="置顶"]) {
content: url('/images/teacher/置顶-选中.png');
}
:deep(.n-dropdown-option:hover img[alt="权限设置"]) {
content: url('/images/teacher/权限设置-选中.png');
}
:deep(.n-dropdown-option:hover img[alt="下载"]) {
content: url('/images/teacher/下载-选中.png');
}
/* 自定义分页器样式 */
.custom-pagination {
display: flex;
justify-content: center;
background: #fff;
padding: 20px 0;
margin-top: auto;
width: 100%;
}
.pagination-content {
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto;
padding: 0 10px;
}
.page-numbers {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
white-space: nowrap;
}
.page-number {
display: inline-block;
min-width: 38px;
height: 38px;
line-height: 38px;
text-align: center;
color: #333;
text-decoration: none;
font-size: 14px;
padding: 0 5px;
margin: 0 4px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.page-number-bordered {
border: 1px solid #d9d9d9;
}
.page-number.active {
background-color: #0088D1;
color: white;
border-color: #0088D1;
}
.page-number:hover:not(.disabled) {
color: #0088D1;
border-color: #0088D1;
}
.page-number.disabled {
color: #ccc;
cursor: not-allowed;
}
.page-number.disabled:hover {
color: #ccc;
border-color: #d9d9d9;
}
.nav-button {
padding: 0 8px;
border: none;
}
.nav-button:hover:not(.disabled) {
color: #0088D1;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="discussion-management">
<div class="content-placeholder">
<h2>讨论管理</h2>
<p>讨论管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.discussion-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="general-management">
<div class="content-placeholder">
<h2>综合管理</h2>
<p>综合管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.general-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="homework-management">
<div class="content-placeholder">
<h2>作业管理</h2>
<p>作业管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.homework-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="notification-management">
<div class="content-placeholder">
<h2>通知管理</h2>
<p>通知管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.notification-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="practice-management">
<div class="content-placeholder">
<h2>练考通管理</h2>
<p>练考通管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.practice-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="question-bank-management">
<div class="content-placeholder">
<h2>题库管理</h2>
<p>题库管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.question-bank-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="statistics-management">
<div class="content-placeholder">
<h2>统计管理</h2>
<p>统计管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.statistics-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>