经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
vue 实现上传组件
来源:jb51  时间:2021/5/31 13:04:55  对本文有异议

1.介绍

效果如下图

 

2.思路

文件上传的两种实现方式

1.From形式

  1. <form
  2. method="post"
  3. enctype="multipart/from-data"
  4. action="api/upload"
  5. >
  6. <input type="file name="file">
  7. <button type="submit">Submit</button>
  8. </form>

form的method属性指定为 "post" 请求,通过HTML表单发送数据给服务器,并返回服务器的修改结果,在这种情况下Content-Type是通过在<form>元素中设置正确的enctype属性。

form的enctype属性规定在发送到服务器之前应该如何对表单数据进行编码。

  • application/x-www-form-urlencoded(默认值):表示在发送前编码所有字符,数据被编码成以"&"分隔的键值对,同时以"="分隔键和值,("name=seven&age=19")。不支持二进制数据。
  • multipart/form-data:支持二进制数据(上传文件时必须指定)

2.JavaScript异步请求形式

我们知道 FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send()方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

  1. var formdata = new FormData(); // 创建FormData对象
  2. formdata.append("name","laotie"); // 通过append()方法添加新的属性值
  3. ... // 更多方法请点下面链接

FormData接口

3.生命周期

上传组件也有它的生命周期

beforeUpload --> uploading --> fileUploaded 或者 uploadedError

4.代码草稿

本例中采用js异步请求的方式开发上传组件

  1. <input type="file" name="file" @change.prevent="handleFileChange">
  2. // 创建一个file类型的input,用于触发文件上传,后面可以把input隐藏掉,自定义好看的样式
  3. // 自定义样式的时候可以用slot区分不同上传状态的样式(loading,success,defult)
  1. const handleFileChange = (e:Event)=>{
  2. const target = e.target as HTMLInputElement
  3. const files = Array.fromtarget.files// 注意这里取得的是一个类数组
  4. if(files){
  5. // 取得文件
  6. const uploadedFile = files[0]
  7. if(!validateFormat) return
  8. // ...这里只是提供一种思路,具体校验不再讲述
  9. // 在这里做一些上传文件前的校验,比如文件格式,大小等,
  10. // 不符合要求的话就不在继续发送请求
  11. const formData = new FormData()
  12. formData.append(uploadedFile.name,uploadedFile)
  13. axios.post('/upload',formData,{
  14. headers:{
  15. // 注意设置编码类型
  16. 'Content-Type': 'multipart/form-data'
  17. }
  18. }).then(res=>{
  19. console.log('上传成功')
  20. }).catch(error =>{
  21. // 文件上传失败
  22. }).finally(()=>{
  23. // 文件上传完成,无论成功还是失败
  24. // 这里可以清除一下input.value
  25. })
  26. }
  27. }

5.具体实现

  1. // Upload.vue
  2. <template>
  3. <div class="upload-container">
  4. <div class="upload-box" @click.prevent="triggerUpload" v-bind="$attrs">
  5. <slot name="loading" v-if="fileStatus==='loading'">
  6. <button class="btn btn-primary">上传中</button>
  7. </slot>
  8. <slot name="uploaded" v-else-if="fileStatus==='success'" :uploadedData="fileData">
  9. <button class="btn btn-primary">上传成功</button>
  10. </slot>
  11. <slot v-else name="default">
  12. <button class="btn btn-primary">点击上传</button>
  13. </slot>
  14. </div>
  15. <input type="file" class="file-input d-none" name="file" ref="uploadInput" @change="hanldeInput"/>
  16. </div>
  17. </template>
  18. <script lang="ts">
  19. import { defineComponent, ref, PropType, watch } from 'vue'
  20. import axios from 'axios'
  21. type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
  22. type FunctionProps = (file:File) => boolean
  23. export default defineComponent({
  24. name: 'Upload',
  25. inheritAttrs: false,
  26. props: {
  27. // 上传的url
  28. action: {
  29. type: String,
  30. required: true
  31. },
  32. // 上传之前的校验,是一个返回布尔值的函数
  33. beforeUpload: {
  34. type: Function as PropType<FunctionProps>
  35. },
  36. // 上传好的数据,用来判断状态或做初始化展示
  37. uploadedData: {
  38. type: Object
  39. }
  40. },
  41. emits: ['file-uploaded-success', 'file-uploaded-error'],
  42. setup(props, ctx) {
  43. const uploadInput = ref<null | HTMLInputElement>(null)
  44. const fileStatus = ref<UploadStatus>(props.uploadedData ? 'success' : 'ready')
  45. const fileData = ref(props.uploadedData)
  46. watch(() => props.uploadedData, (val) => {
  47. if (val) {
  48. fileStatus.value = 'success'
  49. fileData.value = val
  50. }
  51. })
  52. const triggerUpload = () => {
  53. if (uploadInput.value) {
  54. uploadInput.value.click()
  55. }
  56. }
  57. const hanldeInput = (e:Event) => {
  58. const target = e.target as HTMLInputElement
  59. const files = target.files
  60. console.log(target)
  61. if (files) {
  62. const uploadFile = Array.from(files)
  63. const validateFormat = props.beforeUpload ? props.beforeUpload(uploadFile[0]) : true
  64. if (!validateFormat) return
  65. fileStatus.value = 'loading'
  66. const formData = new FormData()
  67. formData.append('file', uploadFile[0])
  68. axios.post(props.action, formData, {
  69. headers: {
  70. 'Content-Type': 'multipart/form-data'
  71. }
  72. }).then(res => {
  73. console.log('文件上传成功', res)
  74. fileStatus.value = 'success'
  75. fileData.value = res.data
  76. ctx.emit('file-uploaded-success', res.data)
  77. }).catch(error => {
  78. console.log('文件上传失败', error)
  79. fileStatus.value = 'error'
  80. ctx.emit('file-uploaded-error', error)
  81. }).finally(() => {
  82. console.log('文件上传完成')
  83. if (uploadInput.value) {
  84. uploadInput.value.value = ''
  85. }
  86. })
  87. }
  88. }
  89.  
  90. return {
  91. uploadInput,
  92. triggerUpload,
  93. hanldeInput,
  94. fileStatus,
  95. fileData
  96. }
  97. }
  98. })
  99. </script>
  100.  

使用示例:

  1. <template>
  2. <div class="create-post-page">
  3. <upload
  4. action="/upload"
  5. :beforeUpload="beforeUpload"
  6. :uploadedData="uploadedData"
  7. @file-uploaded-success="hanldeUploadSuccess"
  8. class="d-flex align-items-center justify-content-center bg-light text-secondary w-100 my-4"
  9. >
  10. <template #uploaded="slotProps">
  11. <div class="uploaded-area">
  12. <img :src="slotProps.uploadedData.data.url"/>
  13. <h3>点击重新上传</h3>
  14. </div>
  15. </template>
  16. <template #default>
  17. <h2>点击上传头图</h2>
  18. </template>
  19. <template #loading>
  20. <div class="d-flex">
  21. <div class="spinner-border text-secondary" role="status">
  22. <span class="sr-only"></span>
  23. </div>
  24. </div>
  25. </template>
  26. </upload>
  27. </div>
  28. </template>
  29. <script lang="ts">
  30. import { defineComponent, ref, onMounted } from 'vue'
  31. import Upload from '../components/Upload.vue'
  32. import createMessage from '../components/createMessage'
  33.  
  34. export default defineComponent({
  35. name: 'CreatePost',
  36. components: { Upload },
  37. setup() {
  38. const uploadedData = ref() //创建一个响应式数据
  39. let imageId = ''
  40. onMounted(() => {
  41. ....
  42. // 这里有逻辑省略了,取到初始化数据image
  43. if (image) {
  44. uploadedData.value = { data: image }
  45. }
  46. })
  47. // 上传前校验,返回布尔值
  48. const beforeUpload = (file:File) => {
  49. const res = beforeUploadCheck(file, {
  50. format: ['image/jpeg', 'image/png'],
  51. size: 1
  52. })
  53. const { error, passed } = res
  54. if (error === 'format') {
  55. createMessage('上传图片只能是JPG/PNG格式!', 'error')
  56. }
  57. if (error === 'size') {
  58. createMessage('上传图片大小不能超过1MB', 'error')
  59. }
  60. return passed
  61. }
  62. // 上传成功后拿到imageId就可以进行后续处理,创建表单啥的
  63. const hanldeUploadSuccess = (res:ResponeseProps<ImageProps>) => {
  64. createMessage(`上传图片ID ${res.data._id}`, 'success')
  65. if (res.data._id) {
  66. imageId = res.data._id
  67. }
  68. }
  69. return {
  70. beforeUpload,
  71. hanldeUploadSuccess,
  72. uploadedData
  73. }
  74. }
  75. })
  76. </script>
  77. <style>
  78. .create-post-page{
  79. padding:0 20px 20px;
  80. }
  81. .create-post-page .upload-box{
  82. height:200px;
  83. cursor: pointer;
  84. overflow: hidden;
  85. }
  86. .create-post-page .upload-box img{
  87. width: 100%;
  88. height: 100%;
  89. object-fit: cover;
  90. }
  91. .uploaded-area{
  92. position: relative;
  93. }
  94. .uploaded-area:hover h3{
  95. display: block;
  96. }
  97. .uploaded-area h3{
  98. display: none;
  99. position: absolute;
  100. color: #999;
  101. text-align: center;
  102. width: 100%;
  103. top:50%
  104. }
  105. </style>
  106.  

以上就是vue 实现上传组件的详细内容,更多关于vue 上传组件的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号