route: 路由调整

This commit is contained in:
QDKF 2025-09-06 17:20:05 +08:00
parent f4991929c6
commit e39010c484
3 changed files with 737 additions and 5 deletions

View File

@ -1,14 +1,736 @@
<template>
<div>
<h1>我的资源</h1>
<div class="resources-content">
<!-- 头部操作区域 -->
<div class="resources-header">
<div class="resources-title">
<h1>我的资源</h1>
</div>
<div class="resources-actions">
<button class="upload-btn" @click="handleUpload">
上传文件
</button>
<button class="new-folder-btn" @click="handleNewFolder">
新建文件夹
</button>
<button class="recycle-bin-btn" @click="handleRecycleBin">
回收站
</button>
<div class="search-container">
<input type="text" class="search-input" placeholder="请输入关键字" v-model="searchKeyword"
@keyup.enter="handleSearch">
<button class="search-btn" @click="handleSearch">
搜索
</button>
</div>
</div>
</div>
<!-- 文件网格 -->
<div class="files-grid">
<div v-for="file in filteredFiles" :key="file.id" class="file-item"
:class="{ 'selected': selectedFiles.includes(file.id) }" @click="handleFileClick(file)"
@contextmenu.prevent="handleRightClick(file, $event)" @mouseenter="hoveredFile = file.id"
@mouseleave="hoveredFile = null">
<!-- 文件操作菜单 -->
<div class="file-menu">
<button class="file-menu-btn" @click.stop="toggleMenu(file.id)">
<img src="/images/profile/more.png" alt="更多操作" class="more-icon">
</button>
<div class="file-menu-dropdown" v-if="showMenuFor === file.id">
<div class="menu-item" @click="handleEdit(file)">
<span class="edit-icon"></span>
编辑
</div>
<div class="menu-item" @click="handleMove(file)">
<span class="move-icon">📁</span>
+ 移动
</div>
<div class="menu-item delete" @click="handleDelete(file)">
<span class="delete-icon">🗑</span>
删除
</div>
</div>
</div>
<!-- 文件图标 -->
<div class="file-icon">
<img :src="getFileIcon(file)" :alt="file.type === 'folder' ? '文件夹图标' : '文件图标'" class="folder-icon">
</div>
<!-- 文件名称 -->
<div class="file-name" :title="file.name">{{ file.name }}</div>
<!-- 文件详情悬浮框 -->
<div class="file-details" v-if="hoveredFile === file.id">
<div class="detail-item">
<span class="detail-label">文件名称</span>
<span class="detail-value">{{ file.name }}</span>
</div>
<div class="detail-item">
<span class="detail-label">大小</span>
<span class="detail-value">{{ formatFileSize(file.size) }}</span>
</div>
<div class="detail-item">
<span class="detail-label">修改时间</span>
<span class="detail-value">{{ formatDate(file.modifiedAt) }}</span>
</div>
</div>
</div>
</div>
<!-- 新建文件夹对话框 -->
<div class="modal-overlay" v-if="showNewFolderModal" @click="closeNewFolderModal">
<div class="modal-content" @click.stop>
<h3>新建文件夹</h3>
<input type="text" v-model="newFolderName" placeholder="请输入文件夹名称" @keyup.enter="confirmNewFolder">
<div class="modal-actions">
<button class="cancel-btn" @click="closeNewFolderModal">取消</button>
<button class="confirm-btn" @click="confirmNewFolder">确定</button>
</div>
</div>
</div>
<!-- 文件上传对话框 -->
<div class="modal-overlay" v-if="showUploadModal" @click="closeUploadModal">
<div class="modal-content" @click.stop>
<h3>上传文件</h3>
<div class="upload-area" @click="triggerFileInput" @dragover.prevent @drop.prevent="handleDrop">
<input ref="fileInput" type="file" multiple @change="handleFileSelect" style="display: none">
<div class="upload-icon">📁</div>
<p>点击选择文件或拖拽文件到此处</p>
</div>
<div class="modal-actions">
<button class="cancel-btn" @click="closeUploadModal">取消</button>
<button class="confirm-btn" @click="confirmUpload">上传</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
//
interface FileItem {
id: string
name: string
type: 'file' | 'folder'
size: number
modifiedAt: string
parentId?: string
}
//
const searchKeyword = ref('')
const selectedFiles = ref<string[]>([])
const showMenuFor = ref<string | null>(null)
const hoveredFile = ref<string | null>(null)
const showNewFolderModal = ref(false)
const showUploadModal = ref(false)
const newFolderName = ref('')
const fileInput = ref<HTMLInputElement>()
const selectedFilesToUpload = ref<File[]>([])
//
const files = ref<FileItem[]>([
{ id: '1', name: '图片', type: 'folder', size: 0, modifiedAt: '2024-01-15 09:25' },
{ id: '2', name: '文档', type: 'folder', size: 0, modifiedAt: '2024-01-14 14:30' },
{ id: '3', name: '视频', type: 'folder', size: 0, modifiedAt: '2024-01-13 16:45' },
{ id: '4', name: '种子', type: 'file', size: 14912, modifiedAt: '2024-01-12 08:25' },
{ id: '5', name: '音频', type: 'folder', size: 0, modifiedAt: '2024-01-11 11:20' },
{ id: '6', name: '文件名称', type: 'file', size: 2048, modifiedAt: '2024-01-10 15:30' },
{ id: '7', name: '文件名称', type: 'file', size: 1024, modifiedAt: '2024-01-09 10:15' },
{ id: '8', name: '文件名称', type: 'file', size: 512, modifiedAt: '2024-01-08 13:45' },
{ id: '9', name: '文件名称', type: 'file', size: 4096, modifiedAt: '2024-01-07 09:30' },
{ id: '10', name: '文件名称', type: 'file', size: 8192, modifiedAt: '2024-01-06 16:20' },
{ id: '11', name: '文件名称', type: 'file', size: 256, modifiedAt: '2024-01-05 12:10' },
{ id: '12', name: '文件名称', type: 'file', size: 1536, modifiedAt: '2024-01-04 14:55' },
{ id: '13', name: '文件名称', type: 'file', size: 3072, modifiedAt: '2024-01-03 11:40' },
{ id: '14', name: '文件名称', type: 'file', size: 6144, modifiedAt: '2024-01-02 08:25' },
{ id: '15', name: '文件名称', type: 'file', size: 128, modifiedAt: '2024-01-01 17:15' },
{ id: '16', name: '文件名称', type: 'file', size: 768, modifiedAt: '2023-12-31 20:30' }
])
//
const filteredFiles = computed(() => {
let result = files.value
//
if (searchKeyword.value) {
result = result.filter((file: FileItem) =>
file.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
}
return result
})
//
const getFileIcon = (file: FileItem) => {
if (file.type === 'folder') {
return '/images/profile/folder.png'
}
//
const ext = file.name.split('.').pop()?.toLowerCase()
switch (ext) {
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return '/images/profile/image.png'
case 'mp4':
case 'avi':
case 'mov':
return '/images/profile/video.png'
case 'mp3':
case 'wav':
case 'flac':
return '/images/profile/audio.png'
case 'pdf':
return '/images/profile/pdf.png'
case 'doc':
case 'docx':
return '/images/profile/word.png'
case 'xls':
case 'xlsx':
return '/images/profile/excel.png'
case 'ppt':
case 'pptx':
return '/images/profile/powerpoint.png'
default:
return '/images/profile/file.png'
}
}
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
const formatDate = (dateString: string) => {
return dateString
}
const handleFileClick = (file: FileItem) => {
if (file.type === 'folder') {
//
console.log('进入文件夹:', file.name)
} else {
//
const index = selectedFiles.value.indexOf(file.id)
if (index > -1) {
selectedFiles.value.splice(index, 1)
} else {
selectedFiles.value.push(file.id)
}
}
}
const handleRightClick = (file: FileItem, event: MouseEvent) => {
event.preventDefault()
showMenuFor.value = file.id
}
const toggleMenu = (fileId: string) => {
showMenuFor.value = showMenuFor.value === fileId ? null : fileId
}
const handleEdit = (file: FileItem) => {
console.log('编辑文件:', file.name)
showMenuFor.value = null
}
const handleMove = (file: FileItem) => {
console.log('移动文件:', file.name)
showMenuFor.value = null
}
const handleDelete = (file: FileItem) => {
if (confirm(`确定要删除 "${file.name}" 吗?`)) {
const index = files.value.findIndex((f: FileItem) => f.id === file.id)
if (index > -1) {
files.value.splice(index, 1)
}
}
showMenuFor.value = null
}
const handleSearch = () => {
console.log('搜索:', searchKeyword.value)
}
const handleUpload = () => {
showUploadModal.value = true
}
const handleNewFolder = () => {
showNewFolderModal.value = true
}
const handleRecycleBin = () => {
console.log('打开回收站')
}
const closeNewFolderModal = () => {
showNewFolderModal.value = false
newFolderName.value = ''
}
const confirmNewFolder = () => {
if (newFolderName.value.trim()) {
const newFolder: FileItem = {
id: Date.now().toString(),
name: newFolderName.value.trim(),
type: 'folder',
size: 0,
modifiedAt: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).replace(/\//g, '-')
}
files.value.push(newFolder)
closeNewFolderModal()
}
}
const closeUploadModal = () => {
showUploadModal.value = false
selectedFilesToUpload.value = []
}
const triggerFileInput = () => {
fileInput.value?.click()
}
const handleFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement
if (target.files) {
selectedFilesToUpload.value = Array.from(target.files)
}
}
const handleDrop = (event: DragEvent) => {
if (event.dataTransfer?.files) {
selectedFilesToUpload.value = Array.from(event.dataTransfer.files)
}
}
const confirmUpload = () => {
if (selectedFilesToUpload.value.length > 0) {
selectedFilesToUpload.value.forEach((file: File) => {
const newFile: FileItem = {
id: Date.now().toString() + Math.random(),
name: file.name,
type: 'file',
size: file.size,
modifiedAt: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}).replace(/\//g, '-')
}
files.value.push(newFile)
})
closeUploadModal()
}
}
//
onMounted(() => {
document.addEventListener('click', () => {
showMenuFor.value = null
})
})
</script>
<style scoped>
</style>
.resources-content {
padding: 0;
background: #F5F7FA;
min-height: 100vh;
width: 100%;
}
/* 头部操作区域 */
.resources-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
background: white;
padding: 20px 30px;
border-bottom: 1px solid #E8E8E8;
width: 100%;
}
.resources-title h1 {
margin: 0;
color: #333;
font-size: 20px;
font-weight: 500;
}
.resources-actions {
display: flex;
align-items: center;
gap: 12px;
}
.upload-btn,
.new-folder-btn,
.recycle-bin-btn {
padding: 6px 16px;
border: 1px solid #0288D1;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
background: white;
color: #0288D1;
min-width: 70px;
text-align: center;
height: 32px;
}
.upload-btn {
background: #0288D1;
color: white;
}
.upload-btn:hover {
background: #0277BD;
}
.new-folder-btn:hover,
.recycle-bin-btn:hover {
background: #F5F8FB;
color: #0288D1;
}
.search-container {
display: flex;
align-items: center;
background: white;
border: 1px solid #D9D9D9;
border-radius: 4px;
overflow: hidden;
height: 32px;
}
.search-input {
padding: 6px 12px;
border: none;
outline: none;
width: 180px;
font-size: 14px;
height: 30px;
}
.search-btn {
padding: 6px 16px;
background: #0288D1;
color: white;
border: none;
cursor: pointer;
transition: background 0.3s ease;
font-size: 14px;
height: 30px;
}
.search-btn:hover {
background: #0277BD;
}
/* 文件网格 */
.files-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 20px;
background: white;
padding: 30px;
margin: 0;
}
.file-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 15px;
border-radius: 2px;
cursor: pointer;
transition: all 0.3s ease;
border: 1.5px solid #D8D8D8;
background: white;
}
.file-item:hover {
background: #F5F8FB;
border-color: #0288D1;
}
.file-item.selected {
background: #E3F2FD;
border-color: #0288D1;
}
.file-menu {
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
}
.file-menu-btn {
width: 6px;
height: 24px;
border: none;
background: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.file-menu-btn:hover {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.more-icon {
width: 16px;
height: 16px;
}
.file-menu-dropdown {
position: absolute;
top: 100%;
right: 0;
background: white;
border: 1px solid #D9D9D9;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 120px;
z-index: 1000;
}
.menu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s ease;
}
.menu-item:hover {
background: #F5F5F5;
}
.menu-item.delete {
color: #FF4D4F;
}
.menu-item.delete:hover {
background: #FFF2F0;
}
.file-icon {
margin-bottom: 8px;
}
.folder-icon {
width: 48px;
height: 48px;
}
.file-name {
font-size: 12px;
color: #333;
text-align: center;
word-break: break-all;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-details {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
background: white;
border: 1px solid #D9D9D9;
border-radius: 6px;
padding: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
min-width: 200px;
}
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
font-size: 12px;
}
.detail-label {
color: #666;
}
.detail-value {
color: #333;
font-weight: 500;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 24px;
min-width: 400px;
max-width: 500px;
}
.modal-content h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 18px;
}
.modal-content input {
width: 100%;
padding: 10px 12px;
border: 1px solid #D9D9D9;
border-radius: 6px;
font-size: 14px;
outline: none;
margin-bottom: 20px;
}
.upload-area {
border: 2px dashed #D9D9D9;
border-radius: 6px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s ease;
margin-bottom: 20px;
}
.upload-area:hover {
border-color: #0288D1;
}
.upload-icon {
font-size: 48px;
margin-bottom: 12px;
display: block;
}
.upload-area p {
margin: 0;
color: #666;
font-size: 14px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.cancel-btn,
.confirm-btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
}
.cancel-btn {
background: #F5F5F5;
color: #666;
}
.cancel-btn:hover {
background: #E6E6E6;
}
.confirm-btn {
background: #0288D1;
color: white;
}
.confirm-btn:hover {
background: #0277BD;
}
/* 响应式设计 */
@media (max-width: 768px) {
.resources-header {
flex-direction: column;
gap: 15px;
padding: 20px;
}
.resources-actions {
flex-wrap: wrap;
justify-content: center;
}
.search-input {
width: 150px;
}
.files-grid {
grid-template-columns: repeat(4, 1fr);
gap: 15px;
padding: 20px;
}
.modal-content {
min-width: 300px;
margin: 20px;
}
}
/* 更小屏幕的响应式设计 */
@media (max-width: 480px) {
.files-grid {
grid-template-columns: repeat(3, 1fr);
gap: 10px;
padding: 15px;
}
}
</style>

View File

@ -84,6 +84,7 @@ import ExamTaking from '@/views/teacher/ExamPages/ExamTaking.vue'
import ExamNoticeBeforeStart from '@/views/teacher/ExamPages/ExamNoticeBeforeStart.vue'
import ChapterEditor from '@/views/teacher/course/ChapterEditor.vue'
import TeacherCourseDetail from '@/views/teacher/course/CourseDetail.vue'
// ========== 路由配置 ==========
const routes: RouteRecordRaw[] = [
@ -509,6 +510,12 @@ const routes: RouteRecordRaw[] = [
component: CourseDetail,
meta: { title: '课程详情' }
},
{
path: '/teacher/course-detail/:id',
name: 'TeacherCourseDetail',
component: TeacherCourseDetail,
meta: { title: '教师端课程详情', requiresAuth: true }
},
{
path: '/course/:id/enrolled',
name: 'CourseDetailEnrolled',

View File

@ -0,0 +1,3 @@
<template>
<div>教师课程详情</div>
</template>