You need to sign in or sign up before continuing.
StudyMaterialsPopup.vue 9.24 KB
<template>
    <van-popup v-model:show="show_materials_popup_model" position="bottom" :style="{ width: '100%', height: '100%' }"
        :close-on-click-overlay="true" :lock-scroll="true">
        <div class="flex flex-col h-full bg-gray-50">
            <div class="bg-white shadow-sm border-b border-gray-100">
                <div class="flex items-center justify-between px-4 py-3">
                    <div class="flex items-center gap-3">
                        <div class="px-1">
                            <h2 class="text-lg font-medium text-gray-900">学习资料</h2>
                            <p class="text-xs text-gray-500">共 {{ files.length }} 个文件</p>
                        </div>
                    </div>
                    <div class="px-2 py-1 rounded-full">
                        <van-button @click="show_materials_popup_model = false" type="default" size="small" round
                            class="w-8 h-8 p-0 bg-gray-100 border-0">
                            <van-icon name="cross" size="16" class="text-gray-600" />
                        </van-button>
                    </div>
                </div>
            </div>

            <div class="flex-1 overflow-y-auto p-4 pb-safe">
                <div v-if="files.length > 0" class="space-y-4">
                    <FrostedGlass v-for="(file, index) in files" :key="index" :bgOpacity="70" blurLevel="md"
                        className="p-5 hover:bg-white/80 transition-all duration-300 hover:shadow-xl hover:scale-[1.02] transform">
                        <div class="flex items-start gap-4 mb-4 p-2">
                            <div
                                class="w-12 h-12 bg-gradient-to-br from-blue-50 to-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0 shadow-sm">
                                <van-icon :name="getFileIcon(file.title || file.name)" class="text-blue-600" :size="22" />
                            </div>
                            <div class="flex-1 min-w-0">
                                <h3 class="text-base font-semibold text-gray-900 mb-2 line-clamp-2">{{ file.title ||
                                    file.name }}</h3>
                                <div class="flex items-center justify-between gap-4 text-sm text-gray-600">
                                    <div class="flex items-center gap-1">
                                        <van-icon name="label-o" size="12" style="margin-right: 0.25rem;" />
                                        <span>{{ getFileType(file.title || file.name) }}</span>
                                        <span class="ml-2">{{ file.size ? (file.size / 1024 / 1024).toFixed(2) + 'MB' :
                                            '' }}</span>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="flex gap-2" style="margin: 1rem;">
                            <button v-if="isPdfFile(file.url)" @click="$emit('openPdf', file)"
                                class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2">
                                <van-icon name="eye-o" size="16" />
                                在线查看
                            </button>
                            <button v-else-if="isAudioFile(file.url)" @click="$emit('openAudio', file)"
                                class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2">
                                <van-icon name="music-o" size="16" />
                                音频播放
                            </button>
                            <button v-else-if="isVideoFile(file.url)" @click="$emit('openVideo', file)"
                                class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2">
                                <van-icon name="video-o" size="16" />
                                视频播放
                            </button>
                            <button v-else-if="isImageFile(file.url)" @click="$emit('openImage', file)"
                                class="btn-primary flex-1 py-2.5 text-sm font-medium flex items-center justify-center gap-2">
                                <van-icon name="photo-o" size="16" />
                                图片预览
                            </button>
                        </div>
                    </FrostedGlass>
                </div>

                <div v-else class="flex flex-col items-center justify-center py-16 px-4">
                    <div class="w-16 h-16 sm:w-20 sm:h-20 bg-gray-100 rounded-full flex items-center justify-center mb-4">
                        <van-icon name="folder-o" :size="28" class="text-gray-400" />
                    </div>
                    <p class="text-gray-500 text-base sm:text-lg mb-2 text-center">暂无学习资料</p>
                    <p class="text-gray-400 text-sm text-center">请联系老师上传相关资料</p>
                </div>
            </div>
        </div>
    </van-popup>
</template>

<script setup>
import { computed } from 'vue';
import FrostedGlass from '@/components/effects/FrostedGlass.vue';

const props = defineProps({
    /** 是否显示资料弹窗 (v-model) */
    showMaterialsPopup: {
        type: Boolean,
        default: false
    },
    /** 资料文件列表 */
    files: {
        type: Array,
        default: () => []
    }
});

const emit = defineEmits([
    /** 更新弹窗显示状态 */
    'update:showMaterialsPopup',
    /** 打开PDF文件 */
    'openPdf',
    /** 打开音频文件 */
    'openAudio',
    /** 打开视频文件 */
    'openVideo',
    /** 打开图片文件 */
    'openImage'
]);

/** @type {import('vue').WritableComputedRef<boolean>} 弹窗显示状态双向绑定 */
const show_materials_popup_model = computed({
    get: () => props.showMaterialsPopup,
    set: (val) => emit('update:showMaterialsPopup', val)
});

/**
 * @description 判断是否为PDF文件
 * @param {string} url 文件URL
 * @returns {boolean}
 */
const isPdfFile = (url) => {
    if (!url || typeof url !== 'string') return false;
    return url.toLowerCase().includes('.pdf');
}

/**
 * @description 判断是否为音频文件
 * @param {string} fileName 文件名
 * @returns {boolean}
 */
const isAudioFile = (fileName) => {
    if (!fileName || typeof fileName !== 'string') return false;
    const extension = fileName.split('.').pop().toLowerCase();
    const audioTypes = ['mp3', 'aac', 'wav', 'ogg'];
    return audioTypes.includes(extension);
}

/**
 * @description 判断是否为视频文件
 * @param {string} fileName 文件名
 * @returns {boolean}
 */
const isVideoFile = (fileName) => {
    if (!fileName || typeof fileName !== 'string') return false;
    const extension = fileName.split('.').pop().toLowerCase();
    const videoTypes = ['mp4', 'avi', 'mov'];
    return videoTypes.includes(extension);
}

/**
 * @description 判断是否为图片文件
 * @param {string} fileName 文件名
 * @returns {boolean}
 */
const isImageFile = (fileName) => {
    if (!fileName || typeof fileName !== 'string') return false;
    const extension = fileName.split('.').pop().toLowerCase();
    const imageTypes = ['jpg', 'jpeg', 'png', 'gif'];
    return imageTypes.includes(extension);
}

/**
 * @description 根据文件扩展名获取对应图标
 * @param {string} fileName 文件名
 * @returns {string} Vant图标名称
 */
const getFileIcon = (fileName) => {
    if (!fileName || typeof fileName !== 'string') {
        return 'description';
    }

    const extension = fileName.split('.').pop().toLowerCase();
    const iconMap = {
        pdf: 'description',
        doc: 'description',
        docx: 'description',
        xls: 'description',
        xlsx: 'description',
        ppt: 'description',
        pptx: 'description',
        txt: 'description',
        zip: 'bag-o',
        rar: 'bag-o',
        '7z': 'bag-o',
        mp3: 'music-o',
        aac: 'music-o',
        wav: 'music-o',
        ogg: 'music-o',
        mp4: 'video-o',
        avi: 'video-o',
        mov: 'video-o',
        jpg: 'photo-o',
        jpeg: 'photo-o',
        png: 'photo-o',
        gif: 'photo-o'
    };
    return iconMap[extension] || 'description';
}

/**
 * @description 获取文件类型描述
 * @param {string} fileName 文件名
 * @returns {string} 文件类型中文描述
 */
const getFileType = (fileName) => {
    if (!fileName || typeof fileName !== 'string') {
        return '未知文件';
    }

    const extension = fileName.split('.').pop().toLowerCase();
    const typeMap = {
        pdf: 'PDF文档',
        doc: 'Word文档',
        docx: 'Word文档',
        xls: 'Excel表格',
        xlsx: 'Excel表格',
        ppt: 'PPT演示',
        pptx: 'PPT演示',
        txt: '文本文件',
        zip: '压缩文件',
        rar: '压缩文件',
        '7z': '压缩文件',
        mp3: '音频文件',
        aac: '音频文件',
        wav: '音频文件',
        ogg: '音频文件',
        mp4: '视频文件',
        avi: '视频文件',
        mov: '视频文件',
        jpg: '图片文件',
        jpeg: '图片文件',
        png: '图片文件',
        gif: '图片文件'
    };
    return typeMap[extension] || '未知文件';
}
</script>