OL-LearnPlatform-Frontend/src/views/teacher/course/ResourceSelectionModal.vue
2025-08-24 18:20:16 +08:00

384 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<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/上传.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;
}
/* 响应式设计 */
@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>