384 lines
9.8 KiB
Vue
384 lines
9.8 KiB
Vue
<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>
|