如何封装一个实用的上传组件

news2025/7/7 22:50:44

前言

马上放假了,时间上相对宽裕,对最近做的东西进行一些总结。今天我们来看一个非常实用的组件,上传组件

我们先从组件的定位、组件的应用场景、组件的特性几个方面进行归纳

定位:

对于上传组件,基础的组件功能属于基础组件范畴,满足上传的基本功能。但业务平台的多样化,促使我们需要在基础组件之上,进行多样化扩展,满足业务功能的需求,这个多样化扩展隶属于业务组件层。但是需要注意几个点,进行组件约定,防止腐烂。

  • 业务公用性
  • 保持功能独立性,尽量解耦
  • 无业务需求,不开发

应用场景:

  • 数据导入,以excel为载体,一般都是成员列表、成绩导入等业务数据,方便用户进行批量操作
  • 文件管理,需要兼容格式较多,PNG/JPG/GIF/PDF/DOC/XLS/txt/md/html/mp4/avi等常用格式,多应用于平台的资源管理类功能
  • 独立业务功能,图片、头像临时上传需要

以上3个应用场景,一般的平台都有应用,但是不同场景对上传组件的功能要求可能不同。

这就需要考虑到业务特性以及产品规划能力。

可根据实际情况,从定位角度对不同场景的进行应用。

属性特征

1、单文件上传、多文件上传2、文件格式校验、文件大小校验3、上传文件维护(显示、移除)

组件实现

目录

对上传组件划分了3层,分别是 Base、Simple、Dialog

Base属于基础组件,提供简单的文件选择,和样式展示

Simple 复合组件-提供系统级别的扩展功能

1、上传提示语2、文件格式、文件大小、模板下载3、已上传列表管理-移除

Dialog 业务型组件

1、展示方式-弹窗2、文件上传功能3、业务参数控制、组件功能控制

入口文件

/*
 * @Description: 
 * @Author: daerduo
 */

import Upload from './Base';
import Simple from './Simple';
import Dialog from './Dialog';

// 复合组件-上传组件
Upload.Simple = Simple
// 业务组件
Upload.Dialog = Dialog

export default Upload; 

使用方式

<Upload.Dialogref="importRefs":options="UploadConfig":request-params="UploadParams"@refresh="refresh"
/>

/**
 * 导入配置
 */
const UploadConfig = {title: '导入成员',download: {type: 'AccountVo',tempName: '导入成员模板.xlsx'}
} 
<Upload.Simple ref="SimpleUploadRefs" :multiple="multiple" :file-rule="FILE_TYPE" :options="SimpleUploadConfig"><template #notes="scope"><span>只允许导入</span><span class="primary">XLS/XLSX</span><span>格式,且大小不超过10M</span><span v-if="getDownloadConfig()">,如果需要也可以<a class="download primary" title="点击下载" @click="downloadTempHandler">下载模板</a></span></template>
</Upload.Simple>

/**
 * 上传文件大小限制
 */
const SimpleUploadConfig = {fileSize: 10
} 

默认是Base,之间使用标签即可

<Upload @fileChange="fileChange" :multiple="multiple"></Upload> 

组件之间的交互

基础上传组件 Base

基础的组件是基于现有的第三方组件库(element)el-upload基础上,做界面扩展和功能增强使用

  • 界面扩展部分

主要是外框内部文字、图标的调整以,在el-upload的默认插槽slot

  • 行为部分

multiple : props属性控制,受外部调用控制,默认是false,单选模式on-change : 添加文件、上传成功和上传失败时都会被调用on-remove:文件列表移除文件时触发

<template><div><el-upload drag :multiple="multiple" :http-request="(val) => { }" :on-change="fileChange" :on-remove="fileRemove"><div class="el-upload__text"><div class="upload-icon primary"><svg-icon icon-class="icon-shangchuan"></svg-icon></div>将文件拖到此处或 <em>点击上传</em></div></el-upload></div>
</template>
<script setup> const props = defineProps({multiple: {type: Boolean,default: false},
})
const emit = defineEmits(["fileChange", "remove"]);

/**
 * 选中文件后回调
 */
const fileChange = (file, fileList) => {emit('fileChange',file,fileList)
}

/**
 * 文件列表移除文件时触发
 */
const fileRemove = (file) => {emit('remove',file)
} </script>
<style lang="scss" scoped> @import "../style/base.scss" </style> 

展示型组件 Simple

对于基础组件,只能满足组件的选择,实际业务应用,通用性不强。展示型组件对基础组件的进一步封装,主要目的是满足业务功能需求,这一点很重要。

因要考虑页面交互方式,交互方式根据业务场景可划分为

1、在组件内嵌套,如表单页面,做为表单项存在2、独立个体,在弹窗内内使用,如数据导入等

需要满足以上2种方式。

<template><div><div class="import-tips"><svg-icon icon-class="icon-mianxing-jieshishuoming"></svg-icon>填写模板时请仔细阅读文件中的说明文字,并严格按照其中所属规则填写,否则可能会导入失败</div><!-- 基础上传组件 --><BaseUpload @fileChange="fileChange" :multiple="multiple"></BaseUpload><!-- 文件格式限制、tips --><div class="notes"><slot name="notes"></slot></div><!-- 选择后-文件列表 --><div class="after" v-for="(item, index) in uploadForm.files" :key="index"><div><span style="color: #283241; margin-left: 10px">{{ item.name }}</span></div><div @click="removeFile(item)" class="remove-file" title="移除文件"><svg-icon icon-class="icon-shanchu"></svg-icon></div></div></div>
</template>
<script setup> import { ElMessage } from "element-plus";
import BaseUpload from "../Base";
import useSimple from "./Simple";
const { uploadForm, clearForm, removeFile } = useSimple();

const emit = defineEmits(["fileChange", "remove"]);

const props = defineProps({multiple: {type: Boolean,default: false,},limit: {type: Number,default: 1,},options: {type: Object,default: {},},fileRule: {type: Array,default: () => {},},
});

// 获取父组件传递值-配置
const getOpions = () => props.options;

/**
 * 下载模板
 * excel
 */
const downloadTemplateHandler = (params) => {const data = {};downloadHandler(data);
};

/**
 * 获取文件校验格式
 */
const getFileRule = () => {return props.fileRule || [];
};

const fileSizeRule = (file, fileSize) => {if (!fileSize) return trueconst isSize = file.size / 1024 / 1024 < fileSizeif (!isSize) {ElMessage({type: 'warning',message: `上传文件大小不能超过${fileSize}M!`})return false}return true
}

/**
 * 选中文件后回调
 */
const fileChange = (file, fileList) => {const uploadFile = uploadForm.files;const fileType = file.raw.type;const limit = props.limit;if (uploadFile && uploadFile.length == limit) {ElMessage({type: "warning",message: `最多上传${limit}个文件!`,});return false;}const fileRule = getFileRule();if (fileRule.length) {const isType = fileRule.includes(fileType);if (!isType) {ElMessage({type: "warning",message: "文件格式错误!",});return false;}}const fileSize = getOpions().fileSizeif(!fileSizeRule(file, fileSize)) returnlet flag = false;uploadForm.files.forEach((item) => {if (file.name == item.name) {flag = true;}});if (!flag) {uploadForm.files.push(file.raw);emit("fileChange", file, fileList);}
};

/**
 * 获取文件列表
 */
const getFiles = () => {return uploadForm.files;
};

/**
 * 清空上传文件
 */
const clearFiles = () => {clearForm();
};

defineExpose({getFiles,clearFiles,
}); </script>
<style lang="scss" scoped> @import "../style/base.scss";
.notes {margin: 10px 0;
} </style> 

hooks部分

import { reactive, ref } from "vue";

export default function useSimple() {const uploadForm = reactive({files: [],});const clearForm = (state) => {uploadForm.files = [];}

 /*** 移除文件*/const removeFile = (file) => {let index = uploadForm.files.findIndex((item) => item.name == file.name);uploadForm.files.splice(index, 1);}return {uploadForm,clearForm,removeFile,};
} 

移除已选择的文件列表,和单个文件的移除,以及向外暴露方法

从上面代码可以看出来,提供了样式多样化部分:

  • 提示说明
  • 文件选择后的列表,移除文件
  • 文件模式(单文件、多文件选择)、上传个数限制、文件格式、文件大小校验

向外暴露方法1、getFiles 获取已上传文件列表2、clearFiles 清楚已选择的文件列表3、事件:fileChange,文件选择后回调、remove移除文件后回调

业务组件 Dialog

这部分是基于展示型组件之上,

如果使用场景在表单内,可直接将展示型组件做为业务容器类组件直接使用。

如果需要弹窗使用,可以在业务功能内做为业务组件进行扩展。

因当前业务组件具备一定通用性,固做为通用性业务组件使用。

当前业务组件,是以导入功能为引导,导入之前,需要先进去导入模板下载,对导入的文件需要进行以下判断

  • 上传文件模式,单文件选择
  • 文件格式限制,只能上传excel文件
  • 文件上传需要按照一定的模板,弹窗内需要提供模板下载
  • 文件上传请求

这是业务功能的要求。业务组件内,页面交互涉及到:

  • 文件上传前的校验控制
  • 上传时的进度或者Loading控制
  • 上传状态后,页面控制等,如关闭窗体、回调触发刷新页面等
<template><div><Dialog.Next :title="getOpions().title || defaultValue.title" ref="uploadDGRefs" width="580px":show-confirm-btn="true" @close="close" @confirm="saveHandler" :limit="limit"><div v-loading="isLoading" :element-loading-text="getOpions().loadingText || defaultValue.loadingText"style="height:440px"><SimpleUpload ref="SimpleUploadRefs" :multiple="multiple" :file-rule="FILE_TYPE" :options="SimpleUploadConfig"><template #notes="scope"><span>只允许导入</span><span class="primary">XLS/XLSX</span><span>格式,且大小不超过10M</span><span v-if="getDownloadConfig()">,如果需要也可以<a class="download primary" title="点击下载" @click="downloadTempHandler">下载模板</a></span></template></SimpleUpload></div></Dialog.Next></div>
</template>
<script setup> import { ElMessage } from "element-plus";
import { ref } from "vue";
import { Dialog } from "@businessComponents";
import SimpleUpload from "../Simple";
import useDialog from "./dialog";
import { Controller } from "./models";
const { uploadForm, isLoading, setLoading, downloadHandler, FILE_TYPE } = useDialog()

const props = defineProps({multiple: {type: Boolean,default: false},limit: {type: Number,default: 1},requestParams: {type: Object,default: {}},options: {type: Object,default: {}},
})

/**
 * 默认值
 */
const defaultValue = {title: '导入',loadingText: '正在导入请耐心等待...'
}

/**
 * 上传文件大小限制
 */
const SimpleUploadConfig = {fileSize: 10
}

// 获取父组件传递值-配置
const getOpions = () => props.options;

const uploadDGRefs = ref(null);
const SimpleUploadRefs = ref(null);
const emit = defineEmits(["refresh"]);

const getDownloadConfig = (params) => {return getOpions().download
}

const downloadTempHandler = (params) => {const config = getDownloadConfig()downloadHandler(config)
}
/**
 * 数据保存
 */
const saveAction = async (formData) => {Controller.uploadFile(formData).then((res) => {if (res) {ElMessage({type: "success",message: "添加成功!",});emit("refresh");close();}setLoading(false)}).catch(() => setLoading(false));
}

/**
 * 获取内部上传组件的文件列表
 */
const getFiles = () => {return SimpleUploadRefs.value.getFiles()
}

/**
 * 获取外部传入的上传参数
 */
const getRequestParams = () => {return props.requestParams || {}
}

// 保存
const saveHandler = () => {const files = getFiles()if (!files || !files.length) {return ElMessage({type: "warning",message: "请上传项目文件!",});}if (isLoading.value) {return ElMessage({type: "warning",message: "文件上传中,请稍等....",});return}setLoading(true)// 拼装参数-文件流 FormDatalet formData = new FormData();files.forEach((item) => {formData.append("file", item);});uploadForm.jsonData = getRequestParams()formData.append("jsonData", JSON.stringify(uploadForm.jsonData));// 上传-数据保存saveAction(formData)
}
// 打开
const load = () => {uploadDGRefs.value.open();
};

/**
 * 关闭
 */
const close = () => {uploadForm.jsonData = {};setLoading(false)SimpleUploadRefs.value.clearFiles()uploadDGRefs.value.close();
};

defineExpose({load,
}) </script>

<style lang="scss" scoped> .primary {color: #00afa5;
}

.download {cursor: pointer;padding-left: 10px;
} </style> 

hooks

import { reactive, ref } from "vue";
import { useExport } from "@hooks";

/**
 * 上传文件格式控制
 * FILE_TYPE = ["application/pdf","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/msword","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","text/plain","application/x-zip-compressed","application/vnd.ms-powerpoint","application/vnd.openxmlformats-officedocument.presentationml.presentation","image/png","image/jpeg"]
 */

export default function useDialog() {const isLoading = ref(false);const setLoading = (state) => {isLoading.value = state}const uploadForm = reactive({jsonData: {name: "",},})/** * 文件格式控制 */const FILE_TYPE = ["application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application/vnd.ms-powerpoint",]/** * 文件下载映射 */const DOWNLOAD_TYPE = {"AccountVo":"com.iss.edu.service.teachcoursemanage.model.vo.AccountVo"}/**
 * 下载模板
 * @param {*} params 
 */const downloadHandler = (config) => {const name = DOWNLOAD_TYPE[config?.type || 'AccountVo']useExport({name: 'downloadExcelTemplate',fileName: config?.tempName || "导入模板.xlsx",fileType: "application/vnd.ms-excel;charset=utf-8"}, { name })}return {isLoading,FILE_TYPE,setLoading,uploadForm,downloadHandler};
} 

CRUD接口请求–models

import { Service,Request } from '@basic-library';
/**
 * @description: 服务器请求控制器
 * @param NO
 * @return {*}
 */
 class MService {// 文件导入async uploadFile(data) {const { success } = await Service.useMultiPart('importMember', data)return success}
}
const Controller = new MService()

export {Controller
} 

代码说明

loading控制部分,使用了vue的指令 v-loding 和 element-loading-text 进行上传中,和文字控制

/**
 * 默认值
 */
const defaultValue = {title: '导入',loadingText: '正在导入请耐心等待...'
} 

文件大小控制

/**
 * 上传文件大小限制
 */
const SimpleUploadConfig = {fileSize: 10
} 

如果属性不存在,则不进行文件大小限制

下载模板控制,是根据外部传入配置控制,使用download对象,如果存在配置则进行功能开启

下载模板的功能,因比较复杂,系统层进行了hooks封装–useExport,可以翻阅之前的文章查看具体实现。 文件流转换为blob,然后使用a连接进行下载

下载的接口,与服务端进行讨论,不同模板的下载类型,是根据不同参数控制(后端微服务映射,比较麻烦)

文件上传,采用了需要拼装文件流-FormData,请求方式支持 Content-Type': 'multipart/form-data'

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/6968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】-- 继承

目录 继承的概念及定义 继承的概念 继承的定义 定义格式 继承基类成员访问方式的变化 基类和派生类对象赋值转换 子类对象可以赋值给父类对象/指针/引用 派生类对象赋值给基类的对象 派生类对象赋值给基类的指针 派生类对象赋值给基类的引用 继承中的作用域 派生类的默认成员函…

IM开源项目OpenIM部署文档-从准备工作到nginx配置

IM开源项目OpenIM部署文档-从准备工作到nginx配置 2022-11-14 22:27OpenIM 一、准备工作 运行环境 linux系统即可&#xff0c; Ubuntu 7.5.0-3ubuntu1~18.04最优 图片视频文件存储 支持cos/MinIO https/wss协议 1. 需申请域名或者子域名&#xff08;web im端登录注册及im…

C# HTML

一 HTML 超文本标记语言 在HTML当中存在着大量的标签&#xff0c;我们用HTML提供的标签&#xff0c;将要显示在网页中的内容包含起来。就构成了我们的网页。 二 CSS CSS 控制网页内容显示的效果。 HTMLCSS静态网页。 JSJquery 动态效果。 三 开始动手写HTML页面 ① 首先在…

XSS进阶二

目录实验目的预备知识实验环境实验步骤一实例四、换一个角度&#xff0c;阳光依旧实验步骤二实例五、限制了我的左手&#xff0c;我还有右手实验步骤三实例六、大胆去思考&#xff0c;小心去求证实验目的 1.深入理解xss工作原理。 2.怎么去绕过规则实现xss。 3.培养学生的独立…

Spring Cloud(十):Spring Cloud Skywalking

链路追踪组件选型 Zipkin是Twitter开源的调用链分析工具&#xff0c;目前基于springcloud sleuth得到了广泛的使用&#xff0c;特点是轻量&#xff0c;使用部署简单。Pinpoint是韩国人开源的基于字节码注入的调用链分析&#xff0c;以及应用监控分析工具。特点是支持多种插件&…

XCTF-web Robots

场景一&#xff1a;Training-WWW-Robots 进入场景&#xff0c;提示关于robots.txt文件 访问robots.txt文件&#xff0c;目录下存在 /fl0g.php 文件&#xff0c;进一步访问得到flag 场景二&#xff1a;robots 根据题目&#xff0c;提示关于robots.txt协议 访问成功&#xff…

api股票数据接口能实现什么功能?

api股票数据接口在量化投资方面能够受到比较多交易者的开发和使用的&#xff0c;主要是得于股票量化交易数据接口的7个策略十档行情&#xff0c;可以实现一键解决炒股难题&#xff0c;和多指标辅助追踪主力&#xff0c;跟主力做强势股&#xff0c;只有在行股票数据接口一键就可…

零样本图像分类综述

零样本图像分类综述 摘要 零样本图像分类指训练集和测试集在数据的类别上没有交集的情况下进行图像分类&#xff0c;该技术是解决类别标签缺失问题的一种有效手段&#xff0c;因此受到了日益广泛的关注&#xff0c;自提出问题至今。零样本图像分类研究已经大致有十年时间啦。…

学完 Fluent 官方基础教程,你离一名合格Fluent 流体工程师还有多远?

作者 | 张杨 仿真秀专栏作者 Fluent软件的学习包含基础部分和进阶部分&#xff0c;通常我们学习Fluent软件&#xff0c;都是从一个最简单的三通管开始的。 图1 Fluent的标准初学案例——三通管混合换热 ANSYS Fluent官方的基础培训课程表&#xff0c;通常只包括以下几个方面…

堆排序在topK场景题中的应用及原理

参考以下文章&#xff1a; 堆排序&#xff08;大顶堆、小顶堆&#xff09;----C语言 006 查找第k大的数——堆结构的初应用寻找最大的K个数&#xff0c;Top K问题的堆实现海量数据查找最大的前k个数(小顶堆) 零、先简单说下处理topK问题的答案&#xff1a; 一般我们说 topK 问…

如此简单易懂的方式 让网站支持PWA

总结起来&#xff0c;网站配置PWA简单步骤为&#xff1a; 1.编写 manifest.json&#xff1b; 2.编写 serviceWorker.js&#xff1b; 3.在 index.html 引入上述两个文件&#xff1b; 4.把上述三个文件放在网站根目录(或者同一目录下)&#xff1b; 5.网站需要部署在https环境才能…

mannose-PEG-Alkyne|甘露糖-聚乙二醇-巯基|巯基修饰甘露糖

mannose-PEG-Alkyne|甘露糖-聚乙二醇-巯基|巯基修饰甘露糖 中文名称&#xff1a;甘露糖-巯基 英文名称&#xff1a;mannose-SH 别称&#xff1a;巯基修饰甘露糖&#xff0c;巯基-甘露糖 西安齐岳生物还可以提供PEG接枝修饰甘露糖&#xff0c;mannose-PEG-Alkyne 甘露糖-聚乙…

Android App开发实战项目之给用户推荐旅游信息图片(附源码 简单易懂)

需要全部源码请点赞关注收藏后评论区留言~~~ 一、需求描述 假定用户打开一个旅游App想看看哪里风景比较优美&#xff0c;那么App应当展示各地的风景名声图片&#xff0c;为了让界面不太呆板&#xff0c;可以考虑交错显示风景图片&#xff0c;接着用户向下拉动页面&#xff0c;…

【ROS】机械人开发二--ROS环境安装

机械人开发二--ROS环境安装一、运行环境二、ROS-melodic安装2.1 设置软件源2.2 设置密钥2.3 安装ROS2.4 环境设置2.5 安装ROS的依赖环境2.6 初始化rosdep三、建立工作空间测试一、运行环境 树莓派4B-4G、VMware15系统都为ubuntu18.04xshell 使用时&#xff0c;通过xshell同时…

10-1.WPF模板

10-1.WPF模板 控件由“算法内容”和“数据内容”决定 算法内容&#xff1a;指控件能展示哪些数据、具有哪些方法、能激发什么事件等&#xff0c;简而言之是控件的功能&#xff0c;一组相关逻辑数据内容&#xff1a;控件所展示的具体数据是什么 在WPF中&#xff0c;模板将数据…

Redis数据类型

1.String(字符串) 在任何一种编程语言中&#xff0c;字符串都是最基础的数据结构&#xff0c;在Redis中String是可以修改的称之为&#xff1a;动态字符串(简称SDS) Redis的内存分配机制&#xff1a; - 当字符串的长度小于1MB时&#xff0c;每次扩容都是加倍现有的空间 - 如果字…

离散数学:图的基本概念

本帖子讨论图的基本概念&#xff0c;这一章&#xff0c;我们将利用有序对和二元关系的概念定义图。图分为了无向图和有向图&#xff0c;他们有共性也有区别&#xff0c;请大家注意体会&#xff0c;用联系和辩证的观点去认识。 1、无向图和有向图 注意无向图和有向图的表示&…

Servlet【 ServletAPI中的会话管理Cookie与Session】

Servlet【 ServletAPI中的会话管理Cookie与Session】&#x1f352;一.回顾Cookie与Session&#x1f34e;1.1 Cookie&#x1f34e;1.2 Session&#x1f34e;1.3Cookie 和 Session 的区别&#x1f352;二.Servlet会话管理操作&#x1f34e;2.1核心方法&#x1f352;三.常见案例实…

【服务器搭建】教程一:没钱买服务器怎么玩 进来看

前言&#xff1a; 最近看到有一些网上的大佬把自己的爱心网页&#xff08;没领到的小伙伴看一下前几篇文章&#xff09;部署到了自己的服务器上&#xff0c;使得可以直接通过链接就实现访问。属实不错&#xff01; 自己内心就产生了这样一个想法&#xff1a;购买一台服务器&a…

Whisper论文阅读笔记

Whisper论文阅读笔记Robust Speech Recognition via Large-Scale Weak Supervision1. 引言2. 方法2.1 数据处理2.2 模型2.3 多任务设置2.4 训练细节3. 实验结果3.1 Zero-shot3.2 多语言语音识别3.3 多语言机器翻译3.4 语种检测3.5 对加性噪声的鲁棒性3.6 长语音转录3.7 人类基线…