diy-upload(开箱即用)

模仿element-plus里的el-upload实现自定义上传文件组件,实现了文件选择、文件类型验证、文件数量限制、文件移除等基本的文件上传功能,并且提供了自定义提示文字的功能。

技术栈:vue3+js

使用方式:

<template>
    <DiyUpload
       multiple
      :limit="3"
      accept="image/jpeg,image/png"
      tip="请上传 JPEG 或 PNG 格式的图片文件"
      @file-selected="onFileSelected"
      @file-removed="onFileRemoved"
    />
</template>
<script setup>
import DiyUpload from "./DiyUpload.vue";

const onFileSelected = (newFileList) => {
    console.log("文件选择后,新的文件列表:", newFileList);
};
const onFileRemoved = (newFileList) => {
    console.log("文件移除后,新的文件列表:", newFileList);
};
</script>
<style scoped>
.diy-upload-wrapper {
    position: absolute;
    left: 20%;
    top: 50%;
    background: linear-gradient(
        270deg,
        rgba(38, 51, 67, 0.8) 16%,
        rgba(16, 29, 47, 0.9) 100%
    );
    padding: 40px 60px;
}
</style>

DiyUpload.vue文件代码:

<template>
    <div class="diy-upload">
        <div class="diy-upload__file-button-wrapper">
            <!-- 自定义按钮 -->
            <button
                aria-label="选择文件"
                class="diy-upload__file-button"
                @click="openFileSelector"
            >
                选择文件
            </button>
            <!-- 隐藏原生文件输入框 -->
            <input
                ref="fileInputRef"
                type="file"
                @change="handleFileChange"
                :multiple="props.multiple"
                :accept="props.accept"
                style="display: none"
            />

            <!-- 显示自定义提示文字 -->
            <div v-if="props.tip" class="diy-upload__file-tip">
                {
  
  { props.tip }}
            </div>
        </div>
        <div v-if="fileList.length > 0" class="diy-upload__file-list">
            <ul class="diy-upload__file-list-ul">
                <li
                    class="diy-upload__file-list-li"
                    v-for="(file, index) in fileList"
                    :key="file.name"
                >
                    <span class="diy-upload__file-list-name">{
  
  {
                        file.name
                    }}</span>
                    <img
                        aria-label="删除文件"
                        class="diy-upload__file-list-icon"
                        @click="removeFile(index)"
                        src="/images/common/delete.png"
                        alt=""
                    />
                </li>
            </ul>
        </div>
        <div v-if="showInvalidTypeMessage">
            存在不允许的文件类型,请选择 {
  
  { props.accept }} 类型的文件。
        </div>
    </div>
</template>

<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";

// 定义组件的 props
const props = defineProps({
    multiple: {
        type: Boolean,
        default: false,
    },
    limit: {
        type: Number,
        default: Infinity,
    },
    accept: {
        type: String,
        default: "*",
    },
    tip: {
        type: String,
        default: "",
    },
});
const emits = defineEmits(["file-selected", "file-removed"]);

// 引用文件输入框
const fileInputRef = ref(null);
// 存储已选择的文件列表
const fileList = ref([]);
// 控制是否显示文件类型不允许的提示信息
const showInvalidTypeMessage = ref(false);

// 打开文件选择对话框
const openFileSelector = () => {
    if (!fileInputRef.value) {
        console.error("文件输入框引用失败");
        return;
    }
    fileInputRef.value.click();
};

// 判断文件类型是否符合要求
const isValidFileType = (file, accept) => {
    const acceptTypes = accept.split(",").map((type) => type.trim());
    const fileType = file.type;
    for (const acceptType of acceptTypes) {
        if (
            acceptType === "*" ||
            acceptType === fileType ||
            (acceptType.startsWith(".") && file.name.endsWith(acceptType))
        ) {
            return true;
        }
    }
    return false;
};

// 处理文件选择事件
const handleFileChange = () => {
    if (!fileInputRef.value) return;

    const newFiles = fileInputRef.value.files;
    const validFiles = [];
    let hasInvalidType = false;

    // 过滤出符合文件类型要求的文件
    for (const file of newFiles) {
        if (isValidFileType(file, props.accept)) {
            validFiles.push(file);
        } else {
            hasInvalidType = true;
        }
    }

    const totalCount = fileList.value.length + validFiles.length;
    if (totalCount <= props.limit) {
        // 未超出数量限制,将符合类型要求的新文件添加到已有的文件列表中
        fileList.value.push(...validFiles);
        // 触发文件选择事件,传递新的文件列表
        emits("file-selected", fileList.value);
    } else {
        // 超出数量限制,给出提示
        if (ElMessage) {
            ElMessage.warning(
                `选择的文件数量超出限制(最多 ${props.limit} 个文件)。`
            );
        } else {
            console.error("ElMessage 未正确引入");
        }
    }
    showInvalidTypeMessage.value = hasInvalidType;
    // 清空输入框的选择,以便下次选择文件时能正确触发 change 事件
    fileInputRef.value.value = "";
};

// 移除文件的方法
const removeFile = (index) => {
    fileList.value.splice(index, 1);
    // 触发文件移除事件,传递新的文件列表
    emits("file-removed", fileList.value);
};
</script>

<style scoped lang="less">
.diy-upload {
    --font-color: #ffffff;
    --button-bg-color: #4890ff;
    --list-bg-color: rgba(49, 92, 155, 0.2);
    .diy-upload__file-button-wrapper {
        display: flex;
        align-items: center;
        margin-left: 6px;
        .diy-upload__file-button {
            font-weight: bold;
            font-size: 12px;
            color: var(--font-color);
            line-height: 14px;
            width: 68px;
            height: 24px;
            background: var(--button-bg-color);
            border-radius: 2px 2px 2px 2px;
        }
        .diy-upload__file-tip {
            font-weight: 400;
            font-size: 12px;
            color: var(--font-color);
            line-height: 14px;
            margin-left: 8px;
        }
    }
    .diy-upload__file-list {
        background: var(--list-bg-color);
        border-radius: 4px 4px 4px 4px;
        padding: 12px 16px;
        margin-top: 8px;
        .diy-upload__file-list-ul {
            .diy-upload__file-list-li {
                display: flex;
                align-items: center;
                justify-content: space-between;
                .diy-upload__file-list-name {
                    font-weight: 400;
                    font-size: 12px;
                    color: var(--font-color);
                    line-height: 14px;
                }
                .diy-upload__file-list-icon {
                    width: 12px;
                    height: 12px;
                    cursor: pointer;
                }
            }
            .diy-upload__file-list-li + .diy-upload__file-list-li {
                margin-top: 8px;
            }
        }
    }
}
</style>

成果图片:

posted @   YAY_tyy  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示