仿写el

编程入门 行业动态 更新时间:2024-10-26 13:19:18

<a href=https://www.elefans.com/category/jswz/34/1769525.html style=仿写el"/>

仿写el

用了那么久的Upload组件,你知道是怎么实现的么,今天就来仿写一个饿了么el-upload vue组件,彻底搞懂前端的文件上传相关知识!

要实现的props

参数说明
action必选参数,上传的地址
headers设置上传的请求头部
multiple是否支持多选文件
data上传时附带的额外参数
name上传的文件字段名
with-credentials支持发送 cookie 凭证信息
show-file-list是否显示已上传文件列表
drag是否启用拖拽上传
accept接受上传的文件类型
on-preview点击文件列表中已上传的文件时的钩子
on-remove文件列表移除文件时的钩子
on-success文件上传成功时的钩子
on-error文件上传失败时的钩子
on-progress文件上传时的钩子
on-change添加文件时被调用
before-upload上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
before-remove删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。
list-type文件列表的类型
auto-upload是否在选取文件后立即进行上传
file-list上传的文件列表, 例如: [{name: ‘food.jpg’, url: ‘.jpg’}]
limit最大允许上传个数
on-exceed文件超出个数限制时的钩子

参考:

这里面有几个重要的点:

  1. input file 的美化
  2. 多选
  3. 拖拽

一个个实现

创建upload组件文件

src/components/upload/index.vue

<template></template>
<script setup>// 属性太多,把props单独放一个文件引入进来import property from './props'const props = defineProps(property)
</script>
<style></style>

./props.js

export default {action: {type: String},headers: {type: Object,default: {}},multiple: {type: Boolean,default: false},data: {type: Object,default: {}},name: {type: String,default: 'file'},'with-credentials': {type: Boolean,default: false},'show-file-list': {type: Boolean,default: true,},drag: {type: Boolean,default: false},accept: {type: String,default: ''},'list-type': {type: String,default: 'text' // text、picture、picture-card},'auto-upload': {type: Boolean,default: true},'file-list': {type: Array,default: []},disabled: {type: Boolean,default: false},limit: {type: Number,default: Infinity},'before-upload': {type: Function,default: () => {return true}},'before-remove': {type: Function,default: () => {return true}}

具体的编写upload组件代码

1. 文件上传按钮的样式

我们都知道,<input type="file">的默认样式是这样的:

很丑,并且无法改变其样式。

解决办法:可以把input隐藏,重新写个按钮点击来触发input的文件选择。

<template><input type="file" id="file" @change="handleChange"><button class="upload-btn" @click="choose">点击上传</button>
</template>
<script setup>// 触发选择文件const choose = () => {document.querySelector('#file').click()}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)}
</script>
<style scoped>#file {display: none;}.upload-btn {border: none;background-color: #07c160;color: #fff;padding: 6px 10px;cursor: pointer;}
</style>

效果:

这样也是可以调起文件选择框,并触发input的onchange事件。

2. 多选

直接在input上加一个Booelan属性multiple,根据props中的值动态设置

顺便把accept属性也加上

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange">
</template>
3. 拖拽

准备一个接收拖拽文件的区域,props传drag=true就用拖拽,否则就使用input上传。

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange"><button class="upload-btn" v-if="!drag" @click="choose">点击上传</button><div v-else class="drag-box" @dragover="handleDragOver"@dragleave="handleDragLeave"@drop="handleDrop"@click="choose":class="{'dragging': isDragging}">将文件拖到此处,或<span>点击上传</span></div>
</template>

dragging用来拖拽鼠标进入时改变样式

<script setup>const isDragging = ref(false)// 拖放进入目标区域const handleDragOver = (event) => {event.preventDefault()isDragging.value = true}const handleDragLeave = (event) => {isDragging.value = false}let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log(files);}
</script>
.drag-box {width: 240px;height: 150px;line-height: 150px;text-align: center;border: 1px dashed #ddd;cursor: pointer;border-radius: 8px;}.drag-box:hover {border-color: cornflowerblue;}.drag-box.dragging {background-color: rgb(131, 161, 216, .2);border-color: cornflowerblue;}.drag-box span {color: cornflowerblue;}


跟使用input上传效果一样

4. 上传到服务器

并实现on-xxx钩子函数

  const emit = defineEmits()const fileList = ref([])let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log('[ files ] >', files)handleBeforeUpload(files)}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)handleBeforeUpload(files)}const handleBeforeUpload = (files) => {if (files.length > props.limit - fileList.value.length) {console.error(`当前限制选择 ${props.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.value.length} 个文件`)emit('on-exceed', files, toRaw(fileList.value))return}// 可以把锁哥文件放到一个formData中一起上传,// 遍历文件一个个上传,这里一个个上传是为了实现钩子函数回调时返回对应的file对象。files.forEach(async file => {emit('on-change', file, files)if (!props.beforeUpload()) {return}if (props.autoUpload) {uploadRequest(file, files)}})}// 手动上传已选择的文件const submit = () => {files.forEach(async file => {uploadRequest(file, files)})}// 保存xhr对象,用于后面取消上传let xhrs = []const uploadRequest = async (file, files) => {let xhr = new XMLHttpRequest();// 调用open函数,指定请求类型与url地址。请求类型必须为POSTxhr.open('POST', props.action);// 设置自定义请求头Object.keys(props.headers).forEach(k => {xhr.setRequestHeader(k, props.headers[k])})// 额外参数const formData = new FormData()formData.append('file', file);Object.keys(props.data).forEach(k => {formData.append(k, props.data[k]);})// 携带cookiexhr.withCredentials = props.withCredentialsxhr.upload.onprogress = (e) => {emit('on-progress', e, file, files)}// 监听状态xhr.onreadystatechange = () => {if (xhr.readyState === 4) {const res = JSON.parse(xhr.response)const fileObj = {name: file.name,percentage: 100,raw: file,response: res,status: 'success',size: file.size,uid: file.uid,}fileList.value.push(fileObj)if (xhr.status === 200 || xhr.status === 201) {emit('on-success', res, fileObj, toRaw(fileList.value))} else {emit('on-error', res, fileObj, toRaw(fileList.value))}}}// 发起请求xhr.send(formData);xhrs.push({xhr,file})}const preview = (file) => {emit('on-preview', file)}const remove = (file, index) => {if (!props.beforeRemove()) {return}fileList.value.splice(index, 1)emit('on-remove', file, fileList.value)}// 取消上传const abort = (file) => {// 通过file对象找到对应的xhr对象,然后调用abort// xhr.abort()}defineExpose({abort,submit})

全部代码

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange"><button class="upload-btn" v-if="!drag" @click="choose">点击上传</button><div v-else class="drag-box" @dragover="handleDragOver"@dragleave="handleDragLeave"@drop="handleDrop"@click="choose":class="{'dragging': isDragging}">将文件拖到此处,或<span>点击上传</span></div><template v-if="showFileList"><template v-if="listType === 'text'"><p class="file-item" v-for="(file, index) in fileList" :key="index" @click="preview(file)"><span>{{file.name}}</span><span class="remove" @click.stop="remove(file, index)">×</span></p></template></template>
</template><script setup>import { ref, toRaw, onMounted } from 'vue'import property from './props'const props = defineProps(property)const emit = defineEmits()const fileList = ref([])const isDragging = ref(false)// 触发选择文件const choose = () => {document.querySelector('#file').click()}// 拖放进入目标区域const handleDragOver = (event) => {event.preventDefault()isDragging.value = true}const handleDragLeave = (event) => {isDragging.value = false}let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log('[ files ] >', files)handleBeforeUpload(files)}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)handleBeforeUpload(files)}const handleBeforeUpload = (files) => {if (files.length > props.limit - fileList.value.length) {console.error(`当前限制选择 ${props.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.value.length} 个文件`)emit('on-exceed', files, toRaw(fileList.value))return}files.forEach(async file => {emit('on-change', file, files)if (!props.beforeUpload()) {return}if (props.autoUpload) {uploadRequest(file, files)}})}// 手动上传已选择的文件const submit = () => {files.forEach(async file => {uploadRequest(file, files)})}let xhrs = []const uploadRequest = async (file, files) => {let xhr = new XMLHttpRequest();// 调用open函数,指定请求类型与url地址。请求类型必须为POSTxhr.open('POST', props.action);// 设置自定义请求头Object.keys(props.headers).forEach(k => {xhr.setRequestHeader(k, props.headers[k])})// 额外参数const formData = new FormData()formData.append('file', file);Object.keys(props.data).forEach(k => {formData.append(k, props.data[k]);})// 携带cookiexhr.withCredentials = props.withCredentialsxhr.upload.onprogress = (e) => {emit('on-progress', e, file, files)}// 监听状态xhr.onreadystatechange = () => {if (xhr.readyState === 4) {const res = JSON.parse(xhr.response)const fileObj = {name: file.name,percentage: 100,raw: file,response: res,status: 'success',size: file.size,uid: file.uid,}fileList.value.push(fileObj)if (xhr.status === 200 || xhr.status === 201) {emit('on-success', res, fileObj, toRaw(fileList.value))} else {emit('on-error', res, fileObj, toRaw(fileList.value))}}}// 发起请求xhr.send(formData);xhrs.push({xhr,file})}const preview = (file) => {emit('on-preview', file)}const remove = (file, index) => {if (!props.beforeRemove()) {return}fileList.value.splice(index, 1)emit('on-remove', file, fileList.value)}// 取消上传const abort = (file) => {// 通过file对象找到对应的xhr对象,然后调用abort// xhr.abort()}defineExpose({abort,submit})
</script><style scoped>#file {display: none;}.upload-btn {border: none;background-color: #07c160;color: #fff;padding: 6px 10px;cursor: pointer;}.drag-box {width: 240px;height: 150px;line-height: 150px;text-align: center;border: 1px dashed #ddd;cursor: pointer;border-radius: 8px;}.drag-box:hover {border-color: cornflowerblue;}.drag-box.dragging {background-color: rgb(131, 161, 216, .2);border-color: cornflowerblue;}.drag-box span {color: cornflowerblue;}.file-item {display: flex;justify-content: space-between;align-items: center;margin-top: 12px;padding: 0 8px;border-radius: 4px;cursor: pointer;}.file-item:hover {background-color: #f5f5f5;color: cornflowerblue;}.file-item .remove {font-size: 20px;}
</style>

更多推荐

仿写el

本文发布于:2023-12-05 18:17:03,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1664926.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:仿写   el

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!