style: 题库首页,课件弹框

This commit is contained in:
guoan 2025-08-23 19:20:14 +08:00
parent 844d5721b7
commit 4d9b5eec2b
23 changed files with 4843 additions and 1995 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View File

@ -3,9 +3,9 @@
<!-- 顶部 -->
<div class="top">
<div class="nav-links">
<a href="" class="active">全部</a>
<a href="">发布中</a>
<a href="">下架中</a>
<a href="" class="active">进行中</a>
<a href="">已结束</a>
<a href="">草稿箱</a>
</div>
<div class="actions">
@ -31,15 +31,13 @@
<div class="options-menu">
<template v-if="course.status === '发布中'">
<a href="#" class="option-item"><img src="/images/teacher/下架.png" alt="">下架</a>
<a href="javascript:void(0)" class="option-item"><img
src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="javascript:void(0)" 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="javascript:void(0)" class="option-item"><img
src="/images/teacher/小编辑.png" alt="">编辑</a>
<a href="javascript:void(0)" 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>
@ -85,18 +83,18 @@ const router = useRouter();
//
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: '/images/teacher/fj.png' },
{ id: 2, name: 'Vue.js 实战教程', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 3, name: 'React 入门到精通', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 4, name: 'Node.js 后端开发', status: '下架中', image: '/images/teacher/fj.png' },
{ id: 5, name: 'TypeScript 高级教程', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 6, name: 'JavaScript 设计模式', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 7, name: 'CSS 动画与特效', status: '下架中', image: '/images/teacher/fj.png' },
{ id: 8, name: 'HTML5 新特性详解', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 9, name: 'Web 性能优化指南', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 10, name: '移动端适配实战', status: '发布中', image: '/images/teacher/fj.png' },
{ id: 11, name: '微信小程序开发', status: '下架中', image: '/images/teacher/fj.png' },
{ id: 12, name: 'Flutter 跨平台开发', status: '发布中', image: '/images/teacher/fj.png' },
]);
@ -108,40 +106,49 @@ const navigateToCreateCourse = () => {
<style scoped>
.course-category{
.course-category {
background-color: #ffffff;
width: 100%;
max-width: 100%;
height: auto;
min-height: 1181px;
margin: 0; /* 移除所有边距 */
margin: 0;
/* 移除所有边距 */
box-sizing: border-box;
padding: 5px; /* 减少内边距 */
position: relative; /* 添加相对定位,使分页按钮的绝对定位基于此容器 */
overflow-x: hidden; /* 防止水平滚动 */
/* 减少内边距 */
position: relative;
/* 添加相对定位,使分页按钮的绝对定位基于此容器 */
overflow-x: hidden;
/* 防止水平滚动 */
}
.top {
white-space: nowrap; /* 防止链接换行 */
padding: 0 8px; /* 进一步减少左右padding */
padding: 20px;
white-space: nowrap;
/* 进一步减少左右padding */
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
width: 100%; /* 确保宽度充满父容器 */
width: 100%;
/* 确保宽度充满父容器 */
box-sizing: border-box;
}
.nav-links {
display: flex;
align-items: center;
}
.actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
.top a{
.top a {
margin-right: 74px;
display: inline-block;
height: 60px;
@ -152,9 +159,11 @@ const navigateToCreateCourse = () => {
text-decoration: none;
position: relative;
}
.top a.active {
color: #1890ff;
}
.top a.active::after {
content: '';
position: absolute;
@ -164,39 +173,46 @@ const navigateToCreateCourse = () => {
height: 3px;
background-color: #1890ff;
}
.create-btn {
background-color: #1890ff;
background-color: #0288D1;
color: white;
border: none;
padding: 6px 16px;
border-radius: 4px;
padding: 7px 16px;
border-radius: 2px;
font-size: 14px;
cursor: pointer;
margin-right: 24px; /* 增加按钮与搜索框间距 */
margin-right: 24px;
/* 增加按钮与搜索框间距 */
transition: background-color 0.3s;
}
.create-btn:hover {
background-color: #40a9ff;
}
.search-container {
display: flex;
align-items: center;
border: 1px solid #d9d9d9;
border-radius: 4px;
border-radius: 2px;
overflow: hidden;
}
.search-container input {
border: none;
padding: 6px 12px;
padding: 7px 12px;
outline: none;
font-size: 14px;
width: 240px; /* 增加搜索框宽度 */
width: 240px;
/* 增加搜索框宽度 */
}
.search-btn {
background-color: #0288D1;
border: none;
border-left: 1px solid #d9d9d9;
padding: 6px 16px;
padding: 8px 16px;
cursor: pointer;
font-size: 16px;
color: #FFFFFF;
@ -206,27 +222,29 @@ const navigateToCreateCourse = () => {
font-style: normal;
text-transform: none;
}
.search-btn:hover {
background-color: #e8e8e8;
}
.course-container{
.course-container {
width: 100%;
margin-top: 30px;
padding: 20px;
box-sizing: border-box;
}
.course-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 默认4列确保100%缩放下不被截断 */
gap: 12px; /* 进一步减少间距 */
grid-template-columns: repeat(4, 1fr);
/* 默认4列确保100%缩放下不被截断 */
gap: 12px;
/* 进一步减少间距 */
margin-bottom: 20px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
padding: 0 3px; /* 减少内边距 */
padding: 0 3px;
/* 减少内边距 */
}
.course-card {
@ -245,6 +263,7 @@ const navigateToCreateCourse = () => {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-5px);
}
.course-image-container {
position: relative;
padding: 0;
@ -278,6 +297,7 @@ const navigateToCreateCourse = () => {
margin: 0;
padding: 0;
}
.more-icon {
font-size: 18px;
font-weight: bold;
@ -287,6 +307,7 @@ const navigateToCreateCourse = () => {
margin: 0;
line-height: 1;
}
.course-image-container img {
width: 13px;
height: 13px;
@ -315,7 +336,7 @@ const navigateToCreateCourse = () => {
background: white;
border: 1px solid #eee;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
display: none;
z-index: 10;
width: auto;
@ -341,10 +362,14 @@ const navigateToCreateCourse = () => {
border-bottom: 1px solid #f0f0f0;
}
.icon-edit, .icon-view, .icon-delete, .icon-more {
.icon-edit,
.icon-view,
.icon-delete,
.icon-more {
margin-right: 4px;
color: #1890ff;
}
.icon-view {
color: #52c41a;
}
@ -356,6 +381,7 @@ const navigateToCreateCourse = () => {
.icon-more {
color: #faad14;
}
.option-item:last-child {
border-bottom: none;
}
@ -373,19 +399,21 @@ const navigateToCreateCourse = () => {
box-sizing: border-box;
padding: 0;
}
.course-info img{
.course-info img {
width: 80%;
height: auto;
aspect-ratio: 150 / 105;
/* 居中 */
display: block;
margin: 5px auto 20px; /* 增加图片下方间距从10px到20px */
margin: 8px auto 10px;
position: relative;
top: -10px;
top: -5px;
object-fit: cover;
object-position: center;
}
.course-name{
.course-name {
width: 80%;
height: 20px;
font-family: AppleSystemUIFont;
@ -395,11 +423,16 @@ const navigateToCreateCourse = () => {
text-align: center;
font-style: normal;
text-transform: none;
white-space: nowrap; /* 强制文本在一行显示 */
overflow: hidden; /* 隐藏超出容器的文本 */
text-overflow: ellipsis; /* 溢出部分用省略号表示 */
margin-top: 0; /* 移除负边距,让文字与图片保持正常距离 */
margin-bottom: 15px; /* 添加底部边距,让文字不挨着底部 */
white-space: nowrap;
/* 强制文本在一行显示 */
overflow: hidden;
/* 隐藏超出容器的文本 */
text-overflow: ellipsis;
/* 溢出部分用省略号表示 */
margin-top: 0px;
/* 文字往上移动:添加负上边距 */
margin-bottom: 10px;
/* 减小底部边距从15px到10px */
}
/* 分页样式 */
@ -432,7 +465,8 @@ const navigateToCreateCourse = () => {
justify-content: center;
gap: 0;
width: 100%;
white-space: nowrap; /* 确保所有页码在一行显示 */
white-space: nowrap;
/* 确保所有页码在一行显示 */
}
.page-number {
@ -490,8 +524,7 @@ const navigateToCreateCourse = () => {
.course-category {
max-width: 1800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
background: #fff;
border-radius: 8px;
}
@ -506,7 +539,9 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 85%;
margin: 8px auto 25px;
margin: 12px auto 8px;
/* 图片往下移动,文字往上移动 */
top: -3px;
}
.course-name {
@ -514,7 +549,10 @@ const navigateToCreateCourse = () => {
height: 24px;
line-height: 24px;
width: 85%;
margin-bottom: 18px;
margin-top: 1px;
/* 文字往上移动 */
margin-bottom: 10px;
/* 减小底部间距 */
}
.section-title {
@ -533,7 +571,6 @@ const navigateToCreateCourse = () => {
.course-category {
max-width: 1500px;
margin: 0 auto;
padding: 15px;
}
.course-grid {
@ -543,7 +580,9 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 82%;
margin: 6px auto 22px;
margin: 10px auto 6px;
/* 图片往下移动,文字往上移动 */
top: -3px;
}
.course-name {
@ -551,7 +590,10 @@ const navigateToCreateCourse = () => {
height: 22px;
line-height: 22px;
width: 82%;
margin-bottom: 16px;
margin-top: -1px;
/* 文字往上移动 */
margin-bottom: 10px;
/* 减小底部间距 */
}
.section-title {
@ -590,12 +632,18 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 80%;
margin: 8px auto 5px;
/* 图片往下移动,文字往上移动 */
top: -3px;
}
.course-name {
font-size: 14px;
width: 80%;
margin-bottom: 15px;
margin-top: -5px;
/* 文字往上移动 */
margin-bottom: 10px;
/* 减小底部间距 */
}
}
@ -612,7 +660,9 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 78%;
margin: 5px auto 18px;
margin: 8px auto 5px;
/* 图片往下移动,文字往上移动 */
top: -3px;
}
.course-name {
@ -620,7 +670,10 @@ const navigateToCreateCourse = () => {
height: 18px;
line-height: 18px;
width: 78%;
margin-bottom: 14px;
margin-top: -5px;
/* 文字往上移动 */
margin-bottom: 10px;
/* 减小底部间距 */
}
.section-title {
@ -647,7 +700,9 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 75%;
margin: 4px auto 15px;
margin: 6px auto 4px;
/* 图片往下移动,文字往上移动 */
top: -2px;
}
.course-name {
@ -655,7 +710,10 @@ const navigateToCreateCourse = () => {
height: 16px;
line-height: 16px;
width: 75%;
margin-bottom: 12px;
margin-top: -4px;
/* 文字往上移动 */
margin-bottom: 8px;
/* 减小底部间距 */
}
.section-title {
@ -705,7 +763,9 @@ const navigateToCreateCourse = () => {
.course-info img {
width: 70%;
margin: 3px auto 12px;
margin: 5px auto 3px;
/* 图片往下移动,文字往上移动 */
top: -2px;
}
.course-name {
@ -713,7 +773,10 @@ const navigateToCreateCourse = () => {
height: 14px;
line-height: 14px;
width: 70%;
margin-bottom: 10px;
margin-top: -3px;
/* 文字往上移动 */
margin-bottom: 6px;
/* 减小底部间距 */
}
.section-title {
@ -782,5 +845,4 @@ const navigateToCreateCourse = () => {
font-size: 12px;
}
}
</style>

View File

@ -2,7 +2,9 @@
<div class="course-management-container">
<!-- 左侧导航栏 -->
<div class="nav-container">
<router-link to="/teacher/course-management" class="nav-item"
<!-- 全部课程可折叠项 -->
<div class="nav-group">
<router-link to="/teacher/course-management" class="nav-item nav-header"
:class="{ active: $route.path === '/teacher/course-management' }">
<span>全部课程</span><i>(10)</i>
<div class="icon-container">
@ -10,8 +12,16 @@
<span class="icon icon-status icon-edit"></span>
<span class="icon icon-status icon-delete"></span>
</div>
<div class="arrow-container" @click.prevent="toggleMainNav">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" class="collapse-arrow"
:style="{ transform: isMainNavExpanded ? 'rotate(-90deg)' : 'rotate(90deg)' }">
<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"
fill="currentColor"></path>
</svg>
</div>
</router-link>
<div class="sub-nav-container">
<div class="sub-nav-container" v-show="isMainNavExpanded">
<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>
@ -32,7 +42,7 @@
</router-link>
<router-link to="/teacher/course-management/course-analysis" class="nav-item sub-nav-item"
:class="{ active: $route.path === '/teacher/course-management/course-analysis' }">
<span>资料分</span>
<span>资料分</span>
<div class="icon-container">
<span class="icon icon-status icon-add"></span>
<span class="icon icon-status icon-edit"></span>
@ -41,6 +51,7 @@
</router-link>
</div>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="content-container">
@ -55,7 +66,16 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import CourseCategory from './CourseComponents/CourseCategory.vue'
//
const isMainNavExpanded = ref(true)
//
const toggleMainNav = () => {
isMainNavExpanded.value = !isMainNavExpanded.value
}
</script>
@ -65,19 +85,49 @@ import CourseCategory from './CourseComponents/CourseCategory.vue'
}
.nav-container {
margin-top: 20px;
margin-top: 8px;
padding: 15px;
width: 274px;
width: 220px;
background-color: #fff;
}
.nav-group {
margin-bottom: 5px;
}
.nav-header {
position: relative;
}
.arrow-container {
position: absolute;
right: 50%;
bottom: 4%;
transform: translateX(50%);
width: 16px;
height: 16px;
cursor: pointer;
z-index: 10;
}
.collapse-arrow {
width: 16px;
height: 16px;
color: #666;
transition: transform 0.2s ease;
}
.collapse-arrow:hover {
color: #0288D1;
}
.sub-nav-container {
margin-left: 10px;
transition: all 0.3s ease;
}
.nav-container .nav-item {
width: 240px;
height: 54px;
width: 190px;
height: 50px;
margin-top: 5px;
margin-bottom: 13px;
display: flex;
@ -88,12 +138,12 @@ import CourseCategory from './CourseComponents/CourseCategory.vue'
color: #333;
text-decoration: none;
transition: all 0.3s ease;
font-size: 18px;
}
font-size: 16px;
}
.sub-nav-item {
width: 220px !important;
font-size: 16px !important;
.sub-nav-item {
width: 170px !important;
font-size: 14px !important;
}
.nav-container .nav-item:hover {
@ -142,7 +192,7 @@ import CourseCategory from './CourseComponents/CourseCategory.vue'
background-repeat: no-repeat;
background-position: center;
display: inline-block;
margin-left: 8px;
margin-left: 4px;
opacity: 0; /* 默认不显示图标 */
transition: opacity 0.3s ease, background-image 0.3s ease;
}

View File

@ -278,7 +278,7 @@ const confirmBatchSet = () => {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
margin-top: 12px;
}
/* 滚动条样式 */

View File

@ -1,10 +1,6 @@
<template>
<n-modal v-model:show="showModal" class="exam-settings-modal" preset="card" :mask-closable="false"
:closable="false" :style="{ width: '1000px' }">
<div class="header">
<span class="header-title">试卷设置</span>
</div>
<n-divider />
<n-modal v-model:show="showModal" class="exam-settings-modal" preset="dialog" title="试卷设置" :mask-closable="false"
:closable="true" :style="{ width: '1000px' }">
<div class="exam-settings-content">
<!-- 试卷名称 -->
@ -25,12 +21,6 @@
</div>
</div>
<!-- 考试人数 -->
<div class="setting-row">
<label class="setting-label">考试人数</label>
<n-input v-model:value="formData.examCount" placeholder="请输入考试人数" class="setting-input" />
</div>
<!-- 试卷分类 -->
<div class="setting-row">
<label class="setting-label">试卷分类</label>
@ -268,10 +258,12 @@
</div>
<!-- 底部按钮 -->
<template #action>
<div class="modal-actions">
<n-button @click="cancelSettings">取消</n-button>
<n-button type="primary" @click="confirmSettings">确定</n-button>
</div>
</template>
</n-modal>
</template>
@ -298,7 +290,6 @@ interface ExamSettings {
participants: 'all' | 'by_school';
selectedClasses: string[];
instructions: string;
examCount: number;
//
enforceOrder: boolean;
@ -372,7 +363,6 @@ const formData = ref<ExamSettings>({
participants: 'all',
selectedClasses: [],
instructions: '',
examCount: 0,
//
enforceOrder: false,
@ -464,12 +454,6 @@ const confirmSettings = () => {
--n-color: #ffffff;
}
.header-title{
color: #000;
font-weight: 400;
font-size: 20px;
}
.exam-settings-content {
max-height: 800px;
overflow-y: auto;
@ -729,7 +713,6 @@ const confirmSettings = () => {
}
.modal-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 12px;

View File

@ -29,15 +29,15 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
import { defineEmits } from 'vue'
// props
const props = defineProps({
show: {
type: Boolean,
required: true
}
})
// const props = defineProps({
// show: {
// type: Boolean,
// required: true
// }
// })
// emits
const emit = defineEmits(['confirm', 'cancel'])

View File

@ -24,44 +24,6 @@
<img :src="activeNavItem === 0 ? '/images/teacher/课程管理(选中).png' : '/images/teacher/课程管理.png'" alt="">
<span>课程管理</span>
</router-link>
<!-- 考试管理 - 可展开菜单 -->
<div class="nav-item" :class="{ active: activeNavItem === 4 }" @click="toggleExamMenu">
<img :src="activeNavItem === 4 ? '/images/teacher/练考通-选中.png' : '/images/teacher/练考通.png'" alt="">
<span>考试管理</span>
<n-icon class="expand-icon" :class="{ expanded: examMenuExpanded }">
<ChevronDownOutline />
</n-icon>
</div>
<!-- 考试管理子菜单 -->
<div class="submenu-container" :class="{ expanded: examMenuExpanded }">
<router-link
to="/teacher/exam-management/question-management"
class="submenu-item"
:class="{ active: activeSubNavItem === 'question-management' }"
@click="setActiveSubNavItem('question-management')"
>
<span>试题管理</span>
</router-link>
<router-link
to="/teacher/exam-management/exam-library"
class="submenu-item"
:class="{ active: activeSubNavItem === 'exam-library' }"
@click="setActiveSubNavItem('exam-library')"
>
<span>试卷管理</span>
</router-link>
<router-link
to="/teacher/exam-management/marking-center"
class="submenu-item"
:class="{ active: activeSubNavItem === 'marking-center' }"
@click="setActiveSubNavItem('marking-center')"
>
<span>阅卷中心</span>
</router-link>
</div>
<router-link to="/teacher/student-management" class="nav-item" :class="{ active: activeNavItem === 1 }"
@click="setActiveNavItem(1)">
@ -88,7 +50,7 @@
<div class="breadcrumb">
<span class="breadcrumb-separator"></span>
<n-breadcrumb>
<n-breadcrumb-item v-for="(item, index) in breadcrumbItems" :key="index" :href="item.path">
<n-breadcrumb-item v-for="(item, index) in breadcrumbItems" :key="index" :to="item.path">
{{ item.title }}
</n-breadcrumb-item>
</n-breadcrumb>
@ -103,43 +65,17 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ChevronDownOutline } from '@vicons/ionicons5'
const width = window.innerWidth;
const height = window.innerHeight;
console.log(`当前屏幕宽度: ${width}px, 高度: ${height}px`);
//
const activeNavItem = ref(0); // 0: , 1: , 2: , 3: , 4:
const activeSubNavItem = ref(''); //
const examMenuExpanded = ref(false); //
const activeNavItem = ref(0); // 0: , 1: , 2: , 3:
const route = useRoute();
const setActiveNavItem = (index: number) => {
activeNavItem.value = index;
//
if (index !== 4) {
examMenuExpanded.value = false;
activeSubNavItem.value = '';
}
}
//
const toggleExamMenu = () => {
examMenuExpanded.value = !examMenuExpanded.value;
activeNavItem.value = 4;
//
if (examMenuExpanded.value && !activeSubNavItem.value) {
activeSubNavItem.value = 'question-management';
}
}
//
const setActiveSubNavItem = (subItem: string) => {
activeSubNavItem.value = subItem;
activeNavItem.value = 4;
examMenuExpanded.value = true;
}
//
@ -172,35 +108,12 @@ const updateActiveNavItem = () => {
const path = route.path;
if (path.includes('course-management')) {
activeNavItem.value = 0; //
examMenuExpanded.value = false;
activeSubNavItem.value = '';
} else if (path.includes('student-management')) {
activeNavItem.value = 1; //
examMenuExpanded.value = false;
activeSubNavItem.value = '';
} else if (path.includes('my-resources')) {
activeNavItem.value = 2; //
examMenuExpanded.value = false;
activeSubNavItem.value = '';
} else if (path.includes('personal-center')) {
activeNavItem.value = 3; //
examMenuExpanded.value = false;
activeSubNavItem.value = '';
} else if (path.includes('exam-management')) {
activeNavItem.value = 4; //
examMenuExpanded.value = true;
//
if (path.includes('question-management')) {
activeSubNavItem.value = 'question-management';
} else if (path.includes('exam-library')) {
activeSubNavItem.value = 'exam-library';
} else if (path.includes('marking-center')) {
activeSubNavItem.value = 'marking-center';
} else {
//
activeSubNavItem.value = 'question-management';
}
}
}
@ -259,7 +172,7 @@ const updateActiveNavItem = () => {
}
.sidebar-container {
width: 294px;
width: 240px;
height: calc(100vh - var(--top-height, 130px));
background: #FFFFFF;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
@ -274,7 +187,7 @@ const updateActiveNavItem = () => {
@media screen and (max-width: 480px) {
.sidebar-container {
--top-height: 80px;
width: 240px;
width: 200px;
}
}
@ -289,35 +202,35 @@ const updateActiveNavItem = () => {
content: '';
position: absolute;
bottom: 0;
left: 20px;
right: 20px;
left: 15px;
right: 15px;
height: 1px;
background-color: #E6E6E6;
}
}
.avatar-container img {
width: 95px;
height: 95px;
.avatar-container img {
width: 80px;
height: 80px;
/* 圆角 */
border-radius: 50%;
margin-top: 55px;
margin-left: 100px;
margin-right: 99;
}
margin-left: 80px;
margin-right: 80px;
}
.avatar-text {
margin-left: 98px;
.avatar-text {
margin-left: 80px;
height: 31px;
font-family: AppleSystemUIFont;
font-size: 22px;
font-size: 20px;
color: #000000;
line-height: 26px;
text-align: left;
font-style: normal;
text-transform: none;
}
}
@media screen and (max-width: 768px) {
@media screen and (max-width: 768px) {
.avatar-container {
height: 180px;
display: flex;
@ -334,9 +247,9 @@ const updateActiveNavItem = () => {
margin: 0;
text-align: center;
}
}
}
@media screen and (max-width: 480px) {
@media screen and (max-width: 480px) {
.avatar-container {
height: 150px;
}
@ -349,29 +262,29 @@ const updateActiveNavItem = () => {
.avatar-text {
font-size: 18px;
}
}
}
.nav-container {
.nav-container {
margin-top: 30px;
/* 鼠标变小手 */
cursor: pointer;
}
}
.nav-container .nav-item {
margin-left: 20px;
width: 254px;
height: 54px;
margin-bottom: 20px;
.nav-container .nav-item {
margin-left: 15px;
width: 210px;
height: 50px;
margin-bottom: 15px;
/* 圆角 */
border-radius: 10px;
display: flex;
align-items: center;
transition: all 0.3s ease;
gap: 8px;
}
}
@media screen and (max-width: 768px) {
@media screen and (max-width: 768px) {
.nav-container {
display: flex;
flex-wrap: wrap;
@ -383,20 +296,9 @@ const updateActiveNavItem = () => {
width: 200px;
margin: 0 10px 15px;
}
.submenu-container {
margin-left: 10px;
width: 200px;
}
.submenu-item {
margin-left: 20px;
padding-left: 30px;
font-size: 14px;
}
}
@media screen and (max-width: 480px) {
@media screen and (max-width: 480px) {
.nav-container .nav-item {
width: 150px;
height: 45px;
@ -406,49 +308,32 @@ const updateActiveNavItem = () => {
.nav-container .nav-item img {
margin-left: 20px;
}
.submenu-container {
margin-left: 5px;
width: 150px;
}
.submenu-item {
margin-left: 10px;
padding-left: 25px;
font-size: 13px;
height: 35px;
}
.expand-icon {
margin-right: 10px;
}
}
.nav-container .nav-item:hover {
.nav-container .nav-item:hover {
background: rgba(102, 183, 227, 0.05);
}
}
/* 添加激活状态样式 */
.nav-container .nav-item.active {
/* 添加激活状态样式 */
.nav-container .nav-item.active {
background: rgba(102, 183, 227, 0.1);
}
}
.nav-container .nav-item.active span {
.nav-container .nav-item.active span {
color: #0C99DA;
}
}
.nav-container .nav-item img {
height: 20px;
margin-left: 50px;
.nav-container .nav-item img {
height: 18px;
margin-left: 40px;
margin-top: 0;
margin-right: 5px;
}
}
.nav-container .nav-item span {
width: 80px;
height: 28px;
.nav-container .nav-item span {
height: 26px;
font-family: AppleSystemUIFont;
font-size: 20px;
font-size: 18px;
color: #666666;
line-height: 23px;
text-align: left;
@ -456,77 +341,12 @@ const updateActiveNavItem = () => {
text-transform: none;
}
/* 展开图标样式 */
.expand-icon {
margin-left: auto;
margin-right: 20px;
transition: transform 0.3s ease;
color: #666;
}
.expand-icon.expanded {
transform: rotate(180deg);
}
/* 子菜单容器 */
.submenu-container {
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease-out;
margin-left: 20px;
width: 254px;
}
.submenu-container.expanded {
max-height: 200px;
}
/* 子菜单项样式 */
.submenu-item {
display: flex;
align-items: center;
height: 40px;
margin-bottom: 8px;
margin-left: 30px;
padding-left: 40px;
border-radius: 8px;
transition: all 0.3s ease;
text-decoration: none;
color: #666;
font-size: 16px;
position: relative;
}
.submenu-item:hover {
background: rgba(102, 183, 227, 0.05);
}
.submenu-item.active {
background: rgba(102, 183, 227, 0.1);
color: #0C99DA;
}
/* 子菜单项前的小圆点 */
.submenu-item::before {
content: '';
width: 6px;
height: 6px;
background-color: #ccc;
border-radius: 50%;
position: absolute;
left: 20px;
transition: background-color 0.3s ease;
}
.submenu-item.active::before {
background-color: #0C99DA;
}
.router-view-container {
flex: 1;
padding: 20px;
padding: 10px 25px;
background: #F5F7FA;
height: calc(100vh - var(--top-height, 130px));
overflow-y: auto;
}
@media screen and (max-width: 768px) {

View File

@ -440,6 +440,7 @@ const handleConfirm = () => {
/* 字体居中 */
display: flex;
align-items: center;
justify-content: center;
/* 小手 */
cursor: pointer;
border-radius: 4px;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
<template>
<div class="course-editor">
<!-- 左侧导航菜单 -->
<div class="sidebar" v-if="showSidebar">
<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'"
@ -16,11 +16,11 @@
</router-link>
<!-- 作业二级导航 -->
<div class="menu-group">
<div class="menu-header" @click="toggleHomework('homework')">
<div class="menu-header" @click="toggleHomework">
<img :src="$route.path.includes('homework') ? '/images/teacher/作业-选中.png' : '/images/teacher/作业.png'"
alt="作业" />
<span>作业</span>
<i class="n-base-icon" :class="{ 'expanded': subMenuArr.homework }">
<i class="n-base-icon" :class="{ 'expanded': homeworkExpanded }">
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<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"
@ -28,7 +28,7 @@
</svg>
</i>
</div>
<div class="submenu" v-show="subMenuArr.homework">
<div class="submenu" v-show="homeworkExpanded">
<router-link :to="`/teacher/course-editor/${courseId}/homework/library`" class="submenu-item"
:class="{ active: $route.path.includes('homework/library') }">
<span>作业库</span>
@ -42,10 +42,9 @@
<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>
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'"
@ -93,38 +92,20 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed, ref, watch } from 'vue'
import { ref } from 'vue'
const route = useRoute()
// ID
const courseId = route.params.id
//
const subMenuArr = ref({
homework: false,
practice: false
})
//
const homeworkExpanded = ref(false)
// /
const toggleHomework = (e: 'homework' | 'practice') => {
subMenuArr.value[e] = !subMenuArr.value[e]
const toggleHomework = () => {
homeworkExpanded.value = !homeworkExpanded.value
}
//
watch(() => route.path, (newPath) => {
if (newPath.includes('practice')) {
subMenuArr.value.practice = true
}else if (newPath.includes('homework')) {
subMenuArr.value.homework = true
}
}, { immediate: true })
//
const showSidebar = computed(() => {
return route.meta.hideSidebar !== true
})
</script>
<style scoped>
@ -137,7 +118,7 @@ const showSidebar = computed(() => {
/* 左侧导航 */
.sidebar {
width: 273px;
width: 220px;
background: #fff;
border-right: 1px solid #e8e8e8;
padding: 20px 15px;
@ -147,14 +128,14 @@ const showSidebar = computed(() => {
.menu-item {
display: flex;
align-items: center;
padding: 15px 20px;
padding: 12px 15px;
margin-bottom: 5px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 3px solid transparent;
text-decoration: none;
color: inherit;
font-size: 18px;
font-size: 16px;
color: #666;
border-radius: 5px;
}
@ -173,15 +154,15 @@ const showSidebar = computed(() => {
}
.menu-item img {
margin-left: 40px;
margin-left: 30px;
margin-top: 1px;
width: 18px;
height: 18px;
margin-right: 10px;
width: 16px;
height: 16px;
margin-right: 14px;
}
.menu-item span {
font-size: 18px;
font-size: 16px;
color: #666;
}
@ -193,11 +174,11 @@ const showSidebar = computed(() => {
.menu-header {
display: flex;
align-items: center;
padding: 15px 20px;
padding: 12px 15px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 3px solid transparent;
font-size: 18px;
font-size: 16px;
color: #666;
border-radius: 5px;
position: relative;
@ -208,15 +189,15 @@ const showSidebar = computed(() => {
}
.menu-header img {
margin-left: 40px;
margin-left: 30px;
margin-top: 1px;
width: 18px;
height: 18px;
margin-right: 10px;
width: 16px;
height: 16px;
margin-right: 14px;
}
.menu-header span {
font-size: 18px;
font-size: 16px;
color: #666;
flex: 1;
}
@ -239,24 +220,24 @@ const showSidebar = computed(() => {
.submenu-item {
display: flex;
align-items: center;
padding: 12px 20px;
padding: 10px 15px;
margin-bottom: 2px;
cursor: pointer;
transition: all 0.3s ease;
border-left: 3px solid transparent;
text-decoration: none;
color: inherit;
font-size: 16px;
font-size: 14px;
color: #666;
border-radius: 5px;
}
.submenu-item::before {
content: '';
width: 18px;
height: 18px;
margin-left: 40px;
margin-right: 10px;
width: 16px;
height: 16px;
margin-left: 30px;
margin-right: 8px;
flex-shrink: 0;
}
@ -274,12 +255,10 @@ const showSidebar = computed(() => {
}
.submenu-item span {
font-size: 16px;
font-size: 14px;
color: #666;
}
/* 右侧内容区域 */
.content-area {
flex: 1;

View File

@ -21,7 +21,7 @@
<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" />
class="file-data-table" :row-class-name="rowClassName" style="width: 100%" />
</n-config-provider>
<!-- 自定义分页器 -->
@ -69,12 +69,8 @@
</div>
<!-- 删除确认模态框 -->
<DeleteFolderConfirmModal
v-if="showDeleteConfirmModal"
:show="showDeleteConfirmModal"
@confirm="confirmDelete"
@cancel="cancelDelete"
/>
<DeleteFolderConfirmModal v-if="showDeleteConfirmModal" :show="showDeleteConfirmModal" @confirm="confirmDelete"
@cancel="cancelDelete" />
</div>
</template>
@ -411,48 +407,48 @@ const handleResize = () => {
}
//
const responsiveColumns = computed(() => {
const width = screenWidth.value
// const responsiveColumns = computed(() => {
// const width = screenWidth.value
//
const baseColumns = {
selection: 40,
index: 80,
name: 270,
size: 100,
creator: 120,
createTime: 180,
actions: 320
}
// // -
// const baseColumns = {
// selection: 50,
// index: 70,
// name: 400, //
// size: 80, //
// creator: 100, //
// createTime: 160, //
// actions: 300 //
// }
//
if (width < 1200) {
//
return {
selection: 35,
index: 60,
name: Math.max(200, width * 0.25),
size: 80,
creator: 100,
createTime: 140,
actions: Math.max(280, width * 0.3)
}
} else if (width < 1600) {
//
return baseColumns
} else {
//
return {
selection: 45,
index: 90,
name: 320,
size: 110,
creator: 130,
createTime: 200,
actions: 360
}
}
})
// //
// if (width < 1200) {
// //
// return {
// selection: 40,
// index: 60,
// name: Math.max(250, width * 0.35), //
// size: 70,
// creator: 80,
// createTime: 130,
// actions: Math.max(250, width * 0.25)
// }
// } else if (width < 1600) {
// //
// return baseColumns
// } else {
// //
// return {
// selection: 50,
// index: 80,
// name: 500, //
// size: 90,
// creator: 120,
// createTime: 180,
// actions: 350
// }
// }
// })
//
onMounted(() => {
@ -468,18 +464,18 @@ onUnmounted(() => {
const columns = computed((): DataTableColumns<FileItem> => [
{
type: 'selection',
width: responsiveColumns.value.selection
width: 50 //
},
{
title: '序号',
key: 'index',
width: responsiveColumns.value.index,
width: 70, //
render: (_row: FileItem, index: number) => index + 1
},
{
title: '名称',
key: 'name',
width: responsiveColumns.value.name,
//
ellipsis: {
tooltip: true
},
@ -524,22 +520,22 @@ const columns = computed((): DataTableColumns<FileItem> => [
{
title: '大小',
key: 'size',
width: responsiveColumns.value.size
width: 80 //
},
{
title: '创建人',
key: 'creator',
width: responsiveColumns.value.creator
width: 100 //
},
{
title: '创建时间',
key: 'createTime',
width: responsiveColumns.value.createTime
width: 160 //
},
{
title: '操作',
key: 'actions',
width: responsiveColumns.value.actions,
width: 300, //
render: (row: FileItem) => {
const buttons = []
@ -891,6 +887,23 @@ const toggleFolder = (folder: FileItem) => {
padding: 40px;
}
/* 表格自适应布局 */
:deep(.file-data-table .n-data-table-wrapper) {
overflow-x: auto;
}
:deep(.file-data-table .n-data-table-base-table) {
width: 100%;
table-layout: fixed;
}
/* 名称列自动占用剩余空间 */
:deep(.file-data-table .n-data-table-td[data-col-key="name"]),
:deep(.file-data-table .n-data-table-th[data-col-key="name"]) {
max-width: 0;
width: auto;
}
/* 表格头部样式 */
:deep(.file-data-table .n-data-table-thead) {
background: #fafafa;
@ -914,9 +927,30 @@ const toggleFolder = (folder: FileItem) => {
padding: 12px 8px;
vertical-align: middle;
text-align: center;
word-break: break-word;
white-space: normal;
}
/* 名称列左对齐 */
/* 序号列居中对齐 */
:deep(.file-data-table .n-data-table-td[data-col-key="index"]) {
text-align: center !important;
}
/* 隐藏序号列的展开占位符,避免影响居中 */
:deep(.file-data-table .n-data-table-td[data-col-key="index"] .n-data-table-expand-placeholder) {
display: none;
}
/* 确保序号列的展开触发器也居中 */
:deep(.file-data-table .n-data-table-td[data-col-key="index"] .n-data-table-expand-trigger) {
margin-right: 4px;
}
:deep(.file-data-table .n-data-table-th[data-col-key="index"]) {
text-align: center !important;
}
/* 名称列居中对齐 */
:deep(.file-data-table .n-data-table-td[data-col-key="name"]) {
text-align: center;
}
@ -925,6 +959,21 @@ const toggleFolder = (folder: FileItem) => {
text-align: center;
}
/* 其他列居中对齐 */
:deep(.file-data-table .n-data-table-td[data-col-key="size"]),
:deep(.file-data-table .n-data-table-td[data-col-key="creator"]),
:deep(.file-data-table .n-data-table-td[data-col-key="createTime"]),
:deep(.file-data-table .n-data-table-td[data-col-key="actions"]) {
text-align: center !important;
}
:deep(.file-data-table .n-data-table-th[data-col-key="size"]),
:deep(.file-data-table .n-data-table-th[data-col-key="creator"]),
:deep(.file-data-table .n-data-table-th[data-col-key="createTime"]),
:deep(.file-data-table .n-data-table-th[data-col-key="actions"]) {
text-align: center !important;
}
:deep(.file-data-table .n-data-table-tr:hover) {
background: #fafafa;
}
@ -1212,6 +1261,11 @@ const toggleFolder = (folder: FileItem) => {
padding: 20px;
}
/* 确保表格横向滚动正常 */
:deep(.file-data-table .n-data-table-wrapper) {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 操作按钮在小屏幕下换行 */
:deep(.file-data-table .n-data-table-td:last-child .n-button) {
margin: 2px;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,210 @@
<template>
<div>
<div class="exam-library-container">
<div class="header-section">
<h1 class="title">试卷库</h1>
<n-space class="actions-group">
<n-button type="primary" @click="handleAddExam">添加试卷</n-button>
<n-button ghost>导入</n-button>
<n-button ghost>导出</n-button>
<n-button type="error" ghost>删除</n-button>
<n-input placeholder="请输入想要搜索的内容" />
<n-button type="primary">搜索</n-button>
</n-space>
</div>
<n-data-table :columns="columns" :data="examData" :row-key="(row: Exam) => row.id"
@update:checked-row-keys="handleCheck" class="exam-table" :single-line="false" />
<div class="pagination-container">
<n-pagination v-model:page="currentPage" :page-count="totalPages" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { h, ref, VNode } from 'vue';
import { NButton, NSpace, useMessage, NDataTable, NPagination, NInput } from 'naive-ui';
import type { DataTableColumns } from 'naive-ui';
import { useRouter } from 'vue-router';
const router = useRouter();
//
type Exam = {
id: number;
name: string;
category: '练习' | '考试';
questionCount: number;
chapter: string;
totalScore: number;
difficulty: '易' | '中' | '难';
status: '发布中' | '未发布' | '已结束';
startTime: string;
endTime: string;
creator: string;
creationTime: string;
};
//
const message = useMessage();
//
const createColumns = ({
handleAction,
}: {
handleAction: (action: string, rowData: Exam) => void;
}): DataTableColumns<Exam> => {
return [
{
type: 'selection',
},
{
title: '序号',
key: 'id',
width: 60,
align: 'center',
},
{
title: '试卷名称',
key: 'name',
},
{
title: '分类',
key: 'category',
},
{
title: '题量',
key: 'questionCount',
},
{
title: '所属章节',
key: 'chapter',
},
{
title: '总分',
key: 'totalScore',
},
{
title: '难度',
key: 'difficulty',
},
{
title: '状态',
key: 'status',
},
{
title: '起止时间',
key: 'startTime',
render(row) {
return `${row.startTime} - ${row.endTime}`;
},
},
{
title: '创建人',
key: 'creator',
},
{
title: '创建时间',
key: 'creationTime',
},
{
title: '操作',
key: 'actions',
render(row) {
const buttons: VNode[] = [];
if (row.status === '发布中') {
buttons.push(
h(NButton, { size: 'small', type: 'primary', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('批阅', row) }, { default: () => '批阅' })
);
} else if (row.status === '未发布') {
buttons.push(
h(NButton, { size: 'small', type: 'primary', style: 'margin: 0 3px;', onClick: () => handleAction('发布', row) }, { default: () => '发布' })
);
}
buttons.push(
h(NButton, { size: 'small', type: 'primary', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('编辑', row) }, { default: () => '编辑' })
);
buttons.push(
h(NButton, { size: 'small', type: 'error', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('删除', row) }, { default: () => '删除' })
);
return h(NSpace, {}, { default: () => buttons });
},
},
];
};
//
const examData = ref<Exam[]>([
{ id: 1, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 2, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 3, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 4, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 5, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 6, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 7, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 8, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
]);
const columns = createColumns({
handleAction: (action, row) => {
message.info(`执行操作: ${action} on row ${row.id}`);
},
});
const checkedRowKeys = ref<Array<string | number>>([]);
const handleCheck = (rowKeys: Array<string | number>) => {
checkedRowKeys.value = rowKeys;
};
//
const currentPage = ref(1);
const totalPages = ref(29); //
const handleAddExam = () => {
//
router.push({ name: 'AddExam' });
};
</script>
<style scoped>
.exam-library-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20px;
border-bottom: 1px solid #E6E6E6;
}
.title {
margin: 0;
font-size: 20px;
font-weight: 500;
}
.actions-group {
display: flex;
align-items: center;
gap: 10px;
}
.exam-table {
margin-top: 20px;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>

View File

@ -0,0 +1,11 @@
<template>
<div>
<h1>阅卷中心</h1>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -19,7 +19,7 @@
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
<n-data-table :columns="columns" :data="sortedHomeworkList" :row-key="rowKey"
:checked-row-keys="selectedHomework" @update:checked-row-keys="handleCheck" :bordered="false"
:single-line="false" size="medium" class="homework-data-table" />
:single-line="false" size="medium" class="homework-data-table" scroll-x="true" />
</n-config-provider>
</div>
@ -94,51 +94,51 @@ const handleResize = () => {
}
//
const responsiveColumns = computed(() => {
const width = screenWidth.value
// const responsiveColumns = computed(() => {
// const width = screenWidth.value
//
const baseColumns = {
selection: 50,
index: 80,
name: 200,
chapter: 180,
class: 150,
creator: 120,
createTime: 180,
actions: 200
}
// //
// const baseColumns = {
// selection: 50,
// index: 80,
// name: 200,
// chapter: 180,
// class: 150,
// creator: 120,
// createTime: 180,
// actions: 200
// }
//
if (width < 1200) {
//
return {
selection: 40,
index: 60,
name: Math.max(150, width * 0.2),
chapter: Math.max(120, width * 0.15),
class: Math.max(100, width * 0.12),
creator: 80,
createTime: 140,
actions: Math.max(160, width * 0.2)
}
} else if (width < 1600) {
//
return baseColumns
} else {
//
return {
selection: 55,
index: 90,
name: 250,
chapter: 220,
class: 180,
creator: 140,
createTime: 200,
actions: 240
}
}
})
// //
// if (width < 1200) {
// //
// return {
// selection: 40,
// index: 60,
// name: Math.max(150, width * 0.2),
// chapter: Math.max(120, width * 0.15),
// class: Math.max(100, width * 0.12),
// creator: 80,
// createTime: 140,
// actions: Math.max(160, width * 0.2)
// }
// } else if (width < 1600) {
// //
// return baseColumns
// } else {
// //
// return {
// selection: 55,
// index: 90,
// name: 250,
// chapter: 220,
// class: 180,
// creator: 140,
// createTime: 200,
// actions: 240
// }
// }
// })
//
onMounted(() => {
@ -272,22 +272,24 @@ const handleCheck = (keys: number[]) => {
selectedHomework.value = keys
}
//
// - 使 minWidth
const columns = computed((): DataTableColumns<HomeworkItem> => [
{
type: 'selection',
width: responsiveColumns.value.selection
minWidth: 50
},
{
title: '序号',
key: 'index',
width: responsiveColumns.value.index,
minWidth: 60,
width: 60,
render: (_, index) => index + 1
},
{
title: '作业名称',
key: 'name',
width: responsiveColumns.value.name,
minWidth: 300,
width: 300,
ellipsis: {
tooltip: true
},
@ -301,7 +303,8 @@ const columns = computed((): DataTableColumns<HomeworkItem> => [
{
title: '所属章节',
key: 'chapter',
width: responsiveColumns.value.chapter,
minWidth: 200,
width: 200,
ellipsis: {
tooltip: true
}
@ -309,7 +312,8 @@ const columns = computed((): DataTableColumns<HomeworkItem> => [
{
title: '绑定班级',
key: 'class',
width: responsiveColumns.value.class,
minWidth: 150,
width: 150,
ellipsis: {
tooltip: true
}
@ -317,29 +321,34 @@ const columns = computed((): DataTableColumns<HomeworkItem> => [
{
title: '创建人',
key: 'creator',
width: responsiveColumns.value.creator
minWidth: 100,
width: 100
},
{
title: '创建时间',
key: 'createTime',
width: responsiveColumns.value.createTime
minWidth: 140,
width: 140
},
{
title: '操作',
key: 'actions',
width: responsiveColumns.value.actions,
minWidth: 200,
width: 200,
render: (row) => {
return h('div', { style: 'display: flex; gap: 8px; align-items: center;' }, [
return h('div', { style: 'display: flex; gap: 6px; align-items: center; justify-content: center; flex-wrap: wrap;' }, [
h(NButton, {
size: 'small',
type: 'info',
secondary: true,
style: 'min-width: 50px;',
onClick: () => editHomework(row.id)
}, { default: () => '编辑' }),
h(NButton, {
size: 'small',
type: 'error',
secondary: true,
style: 'min-width: 50px;',
onClick: () => deleteHomework(row.id)
}, { default: () => '删除' }),
h(NDropdown, {
@ -371,7 +380,8 @@ const columns = computed((): DataTableColumns<HomeworkItem> => [
default: () => h(NButton, {
size: 'small',
type: 'info',
secondary: true
secondary: true,
style: 'min-width: 50px;'
}, { default: () => '更多' })
})
])

View File

@ -1,40 +1,12 @@
<template>
<div class="practice-management">
<div class="content-placeholder">
<h2>考试管理</h2>
<p>考试管理功能正在修改...</p>
<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>
<script setup lang="ts">
//
</script>

View File

@ -1,36 +1,850 @@
<template>
<div class="question-bank-management">
<div class="content-placeholder">
<h2>题库管理</h2>
<p>题库管理功能正在开发中...</p>
<!-- 顶部操作栏 -->
<div class="toolbar">
<h2>全部试题</h2>
<div class="toolbar-actions">
<button class="btn btn-primary" @click="addQuestion">添加试题</button>
<button class="btn btn-new" @click="importQuestions">导入</button>
<div class="dropdown-container">
<button class="btn btn-new" @click="toggleMoreDropdown">更多</button>
<div v-if="showMoreDropdown" class="more-dropdown">
<div class="dropdown-item" @click="categorySettings">分类设置</div>
<div class="dropdown-item" @click="exportQuestions">导出试题</div>
</div>
</div>
<button class="btn btn-danger" @click="deleteSelected">删除</button>
<select v-model="selectedCategory" class="category-select" @change="filterByCategory">
<option value="">分类</option>
<option value="试题分类">试题分类</option>
<option value="分类设置">分类设置</option>
</select>
<div class="search-box">
<input type="text" placeholder="请输入关键词" v-model="searchKeyword" @keyup.enter="searchQuestions" />
<button class="btn btn-search" @click="searchQuestions">搜索</button>
</div>
</div>
</div>
<!-- 题目列表表格 -->
<div class="table-box">
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
<n-data-table :columns="columns" :data="filteredQuestions" :row-key="rowKey"
:checked-row-keys="selectedQuestions" @update:checked-row-keys="handleCheck" :bordered="false"
:single-line="false" size="medium" class="question-data-table" :row-class-name="rowClassName" />
</n-config-provider>
</div>
</div>
</template>
<script setup lang="ts">
//
import { ref, computed, h, onMounted, onUnmounted } from 'vue'
import {
NButton,
NDataTable,
NConfigProvider,
NTag,
useMessage,
zhCN,
dateZhCN
} from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
const message = useMessage()
//
interface Question {
id: number
content: string
type: '单选题' | '填空题' | '多选题' | '判断题' | '简答题'
category: string
difficulty: '易' | '中' | '难'
score: number
creator: string
createTime: string
}
//
const screenWidth = ref(window.innerWidth)
const searchKeyword = ref('')
const selectedCategory = ref('')
const selectedQuestions = ref<number[]>([])
const showMoreDropdown = ref(false)
//
const questionList = ref<Question[]>([
{
id: 1,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 2,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 3,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 4,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 5,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 6,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 7,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 8,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 9,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 10,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 11,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 12,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 13,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 14,
content: '在数据库的三级模式结构中,内模式有...',
type: '填空题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
},
{
id: 15,
content: '在数据库的三级模式结构中,内模式有...',
type: '单选题',
category: '试题分类',
difficulty: '易',
score: 10,
creator: '王建国',
createTime: '2025.08.20 09:20'
}
])
//
const responsiveColumns = computed(() => {
if (screenWidth.value < 1200) {
return {
selection: 40,
index: 60,
content: 200,
type: 80,
category: 100,
difficulty: 60,
score: 60,
creator: 80,
createTime: 120,
actions: 120
}
} else if (screenWidth.value < 1600) {
return {
selection: 50,
index: 80,
content: 280,
type: 100,
category: 120,
difficulty: 80,
score: 80,
creator: 100,
createTime: 150,
actions: 160
}
} else {
return {
selection: 50,
index: 80,
content: 350,
type: 120,
category: 140,
difficulty: 100,
score: 100,
creator: 120,
createTime: 180,
actions: 200
}
}
})
//
const handleResize = () => {
screenWidth.value = window.innerWidth
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
//
const columns = computed((): DataTableColumns<Question> => [
{
type: 'selection',
width: responsiveColumns.value.selection
},
{
title: '序号',
key: 'index',
width: responsiveColumns.value.index,
render: (_row: Question, index: number) => index + 1
},
{
title: '试题内容',
key: 'content',
width: responsiveColumns.value.content,
ellipsis: {
tooltip: true
}
},
{
title: '题型',
key: 'type',
width: responsiveColumns.value.type,
render: (row: Question) => {
return h(NTag, {
type: 'info',
style: {
backgroundColor: 'transparent !important',
color: '#062333 !important',
fontSize: '12px !important',
border: 'none !important',
'--n-border': 'none !important',
'--n-color': 'transparent !important'
}
}, () => row.type)
}
},
{
title: '分类',
key: 'category',
width: responsiveColumns.value.category,
ellipsis: {
tooltip: true
}
},
{
title: '难度',
key: 'difficulty',
width: responsiveColumns.value.difficulty,
render: (row: Question) => {
return h(NTag, {
type: 'info',
style: {
backgroundColor: 'transparent !important',
color: '#062333 !important',
fontSize: '12px !important',
border: 'none !important',
'--n-border': 'none !important',
'--n-color': 'transparent !important'
}
}, () => row.difficulty)
}
},
{
title: '分值',
key: 'score',
width: responsiveColumns.value.score
},
{
title: '创建人',
key: 'creator',
width: responsiveColumns.value.creator,
ellipsis: {
tooltip: true
}
},
{
title: '创建时间',
key: 'createTime',
width: responsiveColumns.value.createTime,
ellipsis: {
tooltip: true
}
},
{
title: '操作',
key: 'actions',
width: responsiveColumns.value.actions,
render: (row: Question) => {
return h('div', { style: { display: 'flex', gap: '8px' } }, [
h(NButton, {
size: 'small',
type: 'info',
ghost: true,
onClick: () => editQuestion(row)
}, () => '编辑'),
h(NButton, {
size: 'small',
type: 'error',
ghost: true,
onClick: () => deleteQuestion(row)
}, () => '删除')
])
}
}
])
//
const filteredQuestions = computed(() => {
let filtered = questionList.value
//
if (selectedCategory.value) {
filtered = filtered.filter(q => q.category === selectedCategory.value)
}
//
if (searchKeyword.value) {
filtered = filtered.filter(q =>
q.content.includes(searchKeyword.value) ||
q.creator.includes(searchKeyword.value)
)
}
return filtered
})
//
// computed
//
const rowKey = (row: Question) => row.id
//
const rowClassName = (row: Question) => {
return selectedQuestions.value.includes(row.id) ? 'selected-row' : ''
}
//
const handleCheck = (keys: number[]) => {
selectedQuestions.value = keys
}
//
const filterByCategory = () => {
//
}
const searchQuestions = () => {
message.info('搜索: ' + searchKeyword.value)
}
const addQuestion = () => {
message.info('添加试题功能')
}
const importQuestions = () => {
message.info('导入试题功能')
}
const toggleMoreDropdown = () => {
showMoreDropdown.value = !showMoreDropdown.value
}
const categorySettings = () => {
showMoreDropdown.value = false
message.info('分类设置功能')
}
const exportQuestions = () => {
showMoreDropdown.value = false
message.info('导出试题功能')
}
const deleteSelected = () => {
if (selectedQuestions.value.length === 0) return
if (confirm(`确定要删除选中的 ${selectedQuestions.value.length} 道试题吗?`)) {
selectedQuestions.value.forEach((id: number) => {
const index = questionList.value.findIndex((q: Question) => q.id === id)
if (index > -1) {
questionList.value.splice(index, 1)
}
})
selectedQuestions.value = []
message.success('删除成功')
}
}
const editQuestion = (question: Question) => {
message.info('编辑试题: ' + question.content.substring(0, 20) + '...')
}
const deleteQuestion = (question: Question) => {
if (confirm('确定要删除这道试题吗?')) {
const index = questionList.value.findIndex(q => q.id === question.id)
if (index > -1) {
questionList.value.splice(index, 1)
message.success('删除成功')
}
}
}
</script>
<style scoped>
.question-bank-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
/* 顶部工具栏 */
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px 0 30px 20px;
background: #fff;
border-bottom: 2px solid #F5F7FA;
margin-right: 20px;
}
.content-placeholder h2 {
font-size: 24px;
.toolbar h2 {
margin: 0;
font-size: 18px;
color: #333;
font-weight: 500;
}
.toolbar-actions {
display: flex;
align-items: center;
gap: 12px;
}
.category-select {
padding: 6px 12px;
border: 1px solid #F1F3F4;
border-radius: 4px;
background: white;
font-size: 14px;
min-width: 160px;
cursor: pointer;
}
.category-select:focus {
border-color: #0088D1;
outline: none;
}
/* 更多下拉菜单样式 */
.dropdown-container {
position: relative;
}
.more-dropdown {
position: absolute;
top: 100%;
left: 0;
background: white;
min-width: 60px;
z-index: 1000;
margin-top: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.dropdown-item {
padding: 8px 8px;
cursor: pointer;
font-size: 10px;
color: #333;
transition: background-color 0.2s;
}
.dropdown-item:hover {
background-color: #f5f5f5;
}
/* 表格容器 */
.table-box {
background: #fff;
padding: 20px;
}
.question-data-table {
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
/* 按钮样式 */
.btn {
padding: 6px 16px;
border: none;
border-radius: 2px;
cursor: pointer;
font-size: 14px;
font-weight: 400;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 32px;
}
.btn-primary {
background: #0088D1;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #0077BB;
}
.btn-new {
background: #fff;
color: #0288D1;
border: 1px solid #0288D1;
}
.btn-new:hover:not(:disabled) {
border-color: #0088D1;
color: #0088D1;
}
.btn-default {
background: #fff;
color: #666;
border: 1px solid #d9d9d9;
}
.btn-default:hover:not(:disabled) {
border-color: #0088D1;
color: #0088D1;
}
.btn-default:disabled {
background: #f5f5f5;
color: #bfbfbf;
cursor: not-allowed;
}
.btn-danger {
background: white;
color: #FF4D4F;
border: 1px solid #FF4D4F;
}
.btn-danger:hover:not(:disabled) {
background: #ff7875;
}
.btn-danger:disabled {
background: #f5f5f5;
color: #bfbfbf;
cursor: not-allowed;
}
.btn-search {
background: #0088D1;
color: white;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.btn-search:hover {
background: #0077BB;
}
/* 搜索框 */
.search-box {
display: flex;
align-items: center;
}
.search-box input {
padding: 6px 12px;
border: 1px solid #d9d9d9;
border-right: none;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
font-size: 14px;
outline: none;
min-width: 200px;
}
.search-box input:focus {
border-color: #0088D1;
}
/* 不再需要分页器样式 */
/* 响应式布局 */
@media (max-width: 1200px) {
.toolbar {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.toolbar-actions {
flex-wrap: wrap;
gap: 8px;
}
.search-box input {
width: 150px;
}
/* 表格在小屏幕下的优化 */
:deep(.question-data-table) {
padding: 20px;
}
/* 操作按钮在小屏幕下换行 */
:deep(.question-data-table .n-data-table-td:last-child .n-button) {
margin: 2px;
font-size: 11px;
padding: 0 8px;
}
}
@media (max-width: 768px) {
.question-bank-management {
padding: 10px;
}
.toolbar {
padding: 15px;
}
.toolbar h2 {
font-size: 16px;
}
.btn {
padding: 6px 12px;
font-size: 12px;
}
.search-box input {
width: 120px;
font-size: 12px;
}
.table-box {
padding: 12px;
}
/* 表格在移动端的优化 */
:deep(.question-data-table) {
padding: 10px;
}
/* 隐藏某些列在移动端 */
:deep(.question-data-table .n-data-table-th[data-col-key="creator"]),
:deep(.question-data-table .n-data-table-td[data-col-key="creator"]) {
display: none;
}
/* 操作按钮在移动端垂直排列 */
:deep(.question-data-table .n-data-table-td:last-child > div) {
flex-direction: column;
gap: 4px;
}
:deep(.question-data-table .n-data-table-td:last-child .n-button) {
width: 100%;
margin: 1px 0;
}
/* 不再需要分页器移动端样式 */
}
@media (max-width: 480px) {
.toolbar-actions {
flex-direction: column;
width: 100%;
}
.search-box {
width: 100%;
}
.search-box input {
width: 100%;
}
.category-select {
width: 100%;
}
/* 进一步隐藏列 */
:deep(.question-data-table .n-data-table-th[data-col-key="createTime"]),
:deep(.question-data-table .n-data-table-td[data-col-key="createTime"]) {
display: none;
}
/* 表格内容在超小屏幕下的优化 */
:deep(.question-data-table .n-data-table-td) {
padding: 8px 4px;
font-size: 12px;
}
:deep(.question-data-table .n-data-table-th) {
padding: 8px 4px;
font-size: 12px;
}
}
/* 表格行样式 */
:deep(.selected-row) {
background-color: #f0f8ff;
}
/* 表格横向滚动优化 */
:deep(.question-data-table .n-data-table-wrapper) {
overflow-x: auto;
}
:deep(.question-data-table .n-data-table-base-table) {
min-width: 100%;
}
/* 确保表格内容不会溢出 */
:deep(.question-data-table .n-data-table-td) {
word-break: break-word;
white-space: normal;
text-align: center;
font-size: 12px;
color: #062333;
}
/* 表头居中对齐 */
:deep(.question-data-table .n-data-table-th) {
text-align: center;
font-size: 12px;
color: #062333;
}
/* 内容列文本溢出处理 */
:deep(.question-data-table .n-data-table-td[data-col-key="content"] span) {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
/* 操作列按钮居中 */
:deep(.question-data-table .n-data-table-td[data-col-key="actions"] > div) {
justify-content: center;
}
</style>

View File

@ -0,0 +1,384 @@
<template>
<n-modal :show="show" @update:show="handleUpdateShow" preset="card" title="我的资源" style="width: 900px; max-width: 90vw;" :closable="false">
<div class="resource-modal-content">
<!-- 筛选和搜索栏 -->
<div class="filter-search-bar">
<div class="filter-section">
<span class="filter-label">类型:</span>
<n-select v-model:value="selectedFileType" :options="fileTypeOptions" placeholder="全部" style="width: 120px;" />
</div>
<div class="search-section">
<span class="search-label">搜索:</span>
<n-input v-model:value="searchKeyword" placeholder="请输入文档名称" style="width: 200px;">
<template #suffix>
<img src="/public/images/teacher/搜索.png" alt="搜索" class="search-icon" @click="searchFiles" />
</template>
</n-input>
</div>
<div class="file-info">
<span class="file-count">已全部加载,{{ filteredFiles.length }}个文件</span>
<n-button type="primary" size="small" @click="uploadNewFile" class="upload-new-file-btn">
<img src="/public/images/teacher/上传2.png" alt="上传" class="upload-icon" />
上传新文件
</n-button>
</div>
</div>
<!-- 文件网格 -->
<div class="file-grid">
<div v-for="file in filteredFiles" :key="file.id" class="file-item" @click="selectFile(file, $event)">
<div class="file-thumbnail">
<img :src="file.thumbnail" :alt="file.name" />
</div>
<div class="file-name">{{ file.name }}</div>
<div class="file-options">
<n-dropdown :options="fileMenuOptions" @select="(key: string) => handleFileMenuSelect(key, file)">
<n-button quaternary size="small" class="file-menu-btn">
<template #icon>
<n-icon size="16">
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>
</svg>
</n-icon>
</template>
</n-button>
</n-dropdown>
</div>
</div>
</div>
</div>
<template #footer>
<div class="modal-footer">
<n-button @click="handleCancel">取消</n-button>
<n-button type="primary" @click="handleConfirm" :disabled="!selectedFile">确定</n-button>
</div>
</template>
</n-modal>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
// Props
interface Props {
show: boolean
}
const props = defineProps<Props>()
// Emits
const emit = defineEmits<{
'update:show': [value: boolean]
'select': [file: any]
}>()
//
const handleUpdateShow = (value: boolean) => {
emit('update:show', value)
}
//
const selectedFileType = ref('all')
const fileTypeOptions = [
{ label: '全部', value: 'all' },
{ label: '文档', value: 'document' },
{ label: '视频', value: 'video' },
{ label: '音频', value: 'audio' },
{ label: '其他', value: 'other' }
]
//
const searchKeyword = ref('')
//
const files = ref([
{ id: 1, name: '开课彩蛋:新开始.pptx', type: 'document', thumbnail: '/images/teacher/fj.png' },
{ id: 2, name: '课程定位与目标.pdf', type: 'document', thumbnail: '/images/teacher/fj.png' },
{ id: 3, name: '教学安排及学习建议.docx', type: 'document', thumbnail: '/images/teacher/fj.png' },
{ id: 4, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 5, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 6, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 7, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 8, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 9, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 10, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 11, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 12, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 13, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 14, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 15, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 16, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 17, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
{ id: 18, name: '风景.mp4', type: 'video', thumbnail: '/images/teacher/fj.png' },
])
//
const filteredFiles = computed(() => {
let result = files.value
//
if (selectedFileType.value !== 'all') {
result = result.filter(file => file.type === selectedFileType.value)
}
//
if (searchKeyword.value.trim()) {
const keyword = searchKeyword.value.toLowerCase()
result = result.filter(file => file.name.toLowerCase().includes(keyword))
}
return result
})
//
const selectedFile = ref<any>(null)
//
const fileMenuOptions = [
{ label: '重命名', key: 'rename' },
{ label: '删除', key: 'delete' }
]
//
const searchFiles = () => {
console.log('搜索文件')
}
//
const uploadNewFile = () => {
console.log('上传新文件')
const newFile = {
id: files.value.length + 1,
name: '新文件.pdf',
type: 'document',
thumbnail: '/images/teacher/document.png'
}
files.value.push(newFile)
}
//
const selectFile = (file: any, event?: Event) => {
selectedFile.value = file
if (event && event.target) {
const target = event.target as HTMLElement
const fileItem = target.closest('.file-item')
if (fileItem) {
document.querySelectorAll('.file-item').forEach(item => {
item.classList.remove('selected')
})
fileItem.classList.add('selected')
}
}
}
//
const handleFileMenuSelect = (key: string, file: any) => {
if (key === 'delete') {
console.log('删除文件:', file)
files.value = files.value.filter(f => f.id !== file.id)
if (selectedFile.value && selectedFile.value.id === file.id) {
selectedFile.value = null
}
} else if (key === 'rename') {
console.log('重命名文件:', file)
}
}
//
const handleCancel = () => {
emit('update:show', false)
selectedFile.value = null
}
//
const handleConfirm = () => {
if (selectedFile.value) {
emit('select', selectedFile.value)
emit('update:show', false)
selectedFile.value = null
}
}
// show
watch(() => props.show, (newVal) => {
if (!newVal) {
selectedFile.value = null
}
})
</script>
<style scoped>
/* 资源选择模态框样式 */
.resource-modal-content {
border-top: 1.5px solid #E6E6E6;
padding: 20px 0;
}
.filter-search-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
gap: 30px;
}
.filter-section, .search-section {
display: flex;
align-items: center;
gap: 8px;
}
.filter-label, .search-label {
font-size: 14px;
color: #333;
font-weight: 500;
}
.file-info {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
flex: 1;
}
.file-count {
font-size: 10px;
color: #999;
}
.file-grid {
padding: 10px 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
max-height: 400px;
overflow-y: auto;
}
.file-item {
min-height: 200px;
background: white;
border: 1.5px solid #D8D8D8;
border-radius: 2px;
padding: 18px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
gap: 8px;
}
.file-item:hover {
border-color: #0288D1;
box-shadow: 0 2px 8px rgba(2, 136, 209, 0.15);
transform: translateY(-2px);
}
.file-item.selected {
border-color: #0288D1;
background: rgba(2, 136, 209, 0.05);
}
.file-thumbnail {
height: 105px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.file-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.file-name {
font-size: 12px;
color: #333;
text-align: center;
line-height: 1.4;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.file-options {
position: absolute;
top: 0px;
right: 0px;
opacity: 1;
transition: opacity 0.3s ease;
}
.file-item:hover .file-options {
opacity: 1;
}
.file-menu-btn {
width: 32px !important;
height: 32px !important;
padding: 0 !important;
background: rgba(255, 255, 255, 0.9) !important;
border: none !important;
border-radius: 0 !important;
}
.upload-new-file-btn {
border-radius: 0;
height: 32px;
}
.upload-icon {
width: 10px;
height: 10px;
margin-right: 4px;
vertical-align: middle;
}
.search-icon {
width: 16px;
height: 16px;
cursor: pointer;
vertical-align: middle;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.filter-search-bar {
flex-direction: column;
align-items: stretch;
gap: 16px;
}
.file-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
}
.file-item {
padding: 8px;
}
.file-thumbnail {
width: 50px;
height: 50px;
}
}
</style>

View File

@ -8,21 +8,13 @@
<!-- 下拉选项 -->
<div v-show="showDropdown" class="upload-methods flex-col">
<label class="local-upload">
<input
type="file"
@change="handleLocalUpload"
style="display: none;"
accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4"
/>
<input type="file" @change="handleLocalUpload" style="display: none;"
accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4" />
本地上传
</label>
<label class="resource-upload">
<input
type="file"
@change="handleResourceUpload"
style="display: none;"
accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4"
/>
<label class="resource-upload" @click="openResourceModal">
<!-- <input type="file" @change="handleResourceUpload" style="display: none;"
accept=".doc,.docx,.pdf,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4" /> -->
资源上传
</label>
</div>
@ -47,11 +39,15 @@
<span class="confirm-btn-text">确定</span>
</div>
</div>
<!-- 资源选择模态框 -->
<ResourceSelectionModal v-model:show="showResourceModal" @select="handleResourceSelection" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ResourceSelectionModal from './ResourceSelectionModal.vue'
//
const showDropdown = ref(false)
@ -73,14 +69,31 @@ const handleLocalUpload = (event: Event) => {
}
//
const handleResourceUpload = (event: Event) => {
const target = event.target as HTMLInputElement
const files = target.files
if (files && files.length > 0) {
console.log('资源上传文件:', files[0])
//
showDropdown.value = false
}
// const handleResourceUpload = (event: Event) => {
// const target = event.target as HTMLInputElement
// const files = target.files
// if (files && files.length > 0) {
// console.log(':', files[0])
// //
// showDropdown.value = false
// }
// }
//
const openResourceModal = () => {
console.log('打开资源选择模态框')
showResourceModal.value = true //
showDropdown.value = false //
}
//
const showResourceModal = ref(false)
//
const handleResourceSelection = (selectedResource: any) => {
console.log('资源选择:', selectedResource)
//
showResourceModal.value = false
}
//