程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • 快速入手

  • 基础组件

  • 表单组件

    • 单选框(Radio)
    • 多选框(Checkbox)
    • 输入框(Input)
    • 计数器(InputNumber)
    • 选择器(Select)
    • 级联选择器(Cascader)
    • 开关(Switch)
    • 滑块(Slider)
    • 时间选择器(TimePicker)
    • 日期选择器(DatePicker)
    • 日期时间选择器(DateTimePicker)
    • 上传(Upload)
      • 1. 基本用法
        • Upload 属性
        • Upload 插槽
        • Upload 方法
      • 2. 常见上传示例
        • 基础文件上传
        • 多文件上传
        • 拖拽上传
        • 显示上传进度
        • 自定义上传实现
      • 3. 其他示例
        • 限制上传文件个数
        • 用户头像上传
        • 照片墙
        • 文件缩略图
        • 上传文件列表控制
      • 4. 自定义头像上传
        • 1. 自定义头像上传实现
        • 2. 封装 AvatarUploader 组件
        • 3. 使用 AvatarUploader 组件
      • 5. Vue 中的 v-model 双向绑定机制
        • 1. v-model 的基本用法
        • 2. v-model 的本质
        • 3. 自定义组件中的 v-model
        • 4. 双向数据绑定的详细实现
    • 评分(Rate)
    • 颜色选择器(ColorPicker)
    • 穿梭框(Transfer)
    • 表单(Form)
    • 表单(Form)校验
  • 数据展示组件

  • 反馈组件

  • 导航组件

  • 其他组件

  • Element-UI
  • 表单组件
scholar
2024-08-12
目录

上传(Upload)

# 上传(Upload)

Element-UI 的上传组件用于通过点击或拖拽上传文件。它支持多文件上传、自定义上传行为等功能,并提供丰富的事件钩子以实现复杂的业务需求。

提示

上传(Upload)组件官方文档:https://element.eleme.cn/#/zh-CN/component/upload (opens new window)

# 1. 基本用法

基本语法:在 Vue 组件中使用 <el-upload> 标签创建一个上传组件,通过 action 属性指定上传的地址,并使用 v-model 绑定上传的文件列表。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    :on-success="handleSuccess"
    :on-error="handleError"
  >
    <el-button slot="trigger" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  },
  methods: {
    handleSuccess(response, file, fileList) {
      console.log('上传成功:', response);
    },
    handleError(err, file, fileList) {
      console.error('上传失败:', err);
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  • action 属性:必选参数,用于指定上传文件的地址。
  • 事件钩子:如 on-success 和 on-error,用于处理上传成功和失败的回调函数。
  • image-20240808060253864

# Upload 属性

参数 说明 类型 可选值 默认值
action 必选参数,上传的地址 string — —
headers 设置上传的请求头部 object — —
multiple 是否支持多选文件 boolean — false
data 上传时附带的额外参数 object — —
name 上传的文件字段名 string — file
with-credentials 支持发送 cookie 凭证信息 boolean — false
show-file-list 是否显示已上传文件列表 boolean — true
drag 是否启用拖拽上传 boolean — false
accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效) string — —
on-preview 点击文件列表中已上传的文件时的钩子 function(file) — —
on-remove 文件列表移除文件时的钩子 function(file, fileList) — —
on-success 文件上传成功时的钩子 function(response, file, fileList) —
on-error 文件上传失败时的钩子 function(err, file, fileList) —
on-progress 文件上传时的钩子 function(event, file, fileList) —
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 function(file, fileList) —
before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传 function(file) — —
before-remove 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除 function(file, fileList) —
list-type 文件列表的类型 string text/picture/picture-card text
auto-upload 是否在选取文件后立即进行上传 boolean — true
file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] array — []
http-request 覆盖默认的上传行为,可以自定义上传的实现 function — —
disabled 是否禁用 boolean — false
limit 最大允许上传个数 number — —
on-exceed 文件超出个数限制时的钩子 function(files, fileList) — —

# Upload 插槽

名称 说明
trigger 触发文件选择框的内容
tip 提示说明文字

# Upload 方法

方法名 说明 参数
clearFiles 清空已上传的文件列表(该方法不支持在 before-upload 中调用) —
abort 取消上传请求 file: fileList 中的 file 对象
submit 手动上传文件列表 —

# 2. 常见上传示例

# 基础文件上传

通过 action 属性指定上传地址,并使用 v-model 绑定上传文件列表。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
  >
    <el-button slot="trigger" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 基本用法:用户点击按钮选择文件进行上传,上传的文件列表显示在组件内。
  • image-20240808060310851

# 多文件上传

通过 multiple 属性支持多文件上传。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    multiple
  >
    <el-button slot="trigger" type="primary">点击上传多个文件</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • 多文件上传:通过 multiple 属性支持一次选择多个文件上传。
  • image-20240808060405986

# 拖拽上传

通过 drag 属性支持拖拽上传。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    drag
  >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 拖拽上传:通过 drag 属性支持用户将文件拖拽到上传区域进行上传。
  • image-20240808060508037

# 显示上传进度

通过 on-progress 事件处理上传进度。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    :on-progress="handleProgress"
  >
    <el-button slot="trigger" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  },
  methods: {
    handleProgress(event, file, fileList) {
      console.log('上传进度:', event.percent, '%');
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 上传进度:通过 on-progress 事件处理,实时显示上传进度。
  • image-20240808060623259

# 自定义上传实现

通过 http-request 方法自定义上传行为。

<template>
  <el-upload
    :http-request="customRequest"
  >
    <el-button slot="trigger" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  methods: {
    customRequest({file, onSuccess}) {
      setTimeout(() => {
        console.log('自定义上传实现:', file);
        onSuccess('上传成功');
      }, 1000);
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 自定义上传:通过 http-request 方法覆盖默认的上传行为,实现自定义上传逻辑。
  • image-20240808060807607

# 3. 其他示例

# 限制上传文件个数

通过 limit 和 on-exceed 属性限制上传文件的个数,并定义超出限制时的行为。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    :limit="3"
    :on-exceed="handleExceed"
  >
    <el-button slot="trigger" type="primary">点击上传(最多3个文件)</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  },
  methods: {
    handleExceed(files, fileList) {
      console.log('文件超出限制:', files, fileList);
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 文件限制:通过 limit 属性限制最多上传文件个数,并通过 on-exceed 事件处理超出限制的情况。
  • image-20240808080421759

# 用户头像上传

使用 before-upload 限制用户上传的图片格式和大小。

<template>
  <el-upload
    class="avatar-uploader"
    action="https://jsonplaceholder.typicode.com/posts/"
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload"
  >
    <img v-if="imageUrl" :src="imageUrl" class="avatar" />
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      imageUrl: ''
    };
  },
  methods: {
    handleAvatarSuccess(res, file) {
      this.imageUrl = URL.createObjectURL(file.raw);
    },
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg';
      const isLt2M = file.size / 1024 / 1024 < 2;

      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG 格式!');
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!');
      }
      return isJPG && isLt2M;
    }
  }
};
</script>

<style scoped>
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  width: 178px;
  height: 178px;
}
.avatar-uploader .el-upload:hover {
  border-color: #409EFF;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  line-height: 178px;
  text-align: center;
}
.avatar {
  width: 178px;
  height: 178px;
  display: block;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  • 头像上传:通过 before-upload 事件限制上传的图片格式和大小。
  • image-20240808083732794

# 照片墙

使用 list-type 属性设置文件列表的样式。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    list-type="picture-card"
    :on-preview="handlePictureCardPreview"
    :on-remove="handleRemove"
  >
    <i class="el-icon-plus"></i>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
  <el-dialog :visible.sync="dialogVisible">
    <img width="100%" :src="dialogImageUrl" alt="">
  </el-dialog>
</template>

<script>
export default {
  data() {
    return {
      dialogImageUrl: '',
      dialogVisible: false
    };
  },
  methods: {
    handleRemove(file, fileList) {
      console.log(file, fileList);
    },
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  • 照片墙:通过 list-type="picture-card" 属性设置文件列表为图片卡片样式。
  • image-20240808080614576

# 文件缩略图

使用 scoped-slot 设置缩略图模版。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    list-type="picture-card"
    :auto-upload="false"
  >
    <i slot="default" class="el-icon-plus"></i>
    <div slot="file" slot-scope="{file}">
      <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" style="width: 100px; height: 100px;">
      <span class="el-upload-list__item-actions">
        <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
          <i class="el-icon-zoom-in"></i>
        </span>
        <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
          <i class="el-icon-download"></i>
        </span>
        <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
          <i class="el-icon-delete"></i>
        </span>
      </span>
    </div>
  </el-upload>
  <el-dialog :visible.sync="dialogVisible">
    <img width="100%" :src="dialogImageUrl" alt="">
  </el-dialog>
</template>

<script>
export default {
  data() {
    return {
      dialogImageUrl: '',
      dialogVisible: false,
      disabled: false
    };
  },
  methods: {
    handleRemove(file) {
      console.log(file);
    },
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },
    handleDownload(file) {
      console.log(file);
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  • 文件缩略图:通过 scoped-slot 自定义文件列表的缩略图显示样式。
  • image-20240808084130334

# 上传文件列表控制

通过 on-change 钩子函数对列表进行控制。

<template>
  <el-upload
    action="https://jsonplaceholder.typicode.com/posts/"
    :file-list="fileList"
    :on-change="handleChange"
  >
    <el-button slot="trigger" type="primary">点击上传</el-button>
    <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
  </el-upload>
</template>

<script>
export default {
  data() {
    return {
      fileList: []
    };
  },
  methods: {
    handleChange(file, fileList) {
      console.log('文件状态变化:', file, fileList);
      this.fileList = fileList; // 更新文件列表
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • 文件列表控制:通过 on-change 事件处理,动态更新文件列表。
  • image-20240808084440022

总结

  • 多种上传方式:支持点击上传、拖拽上传、多文件上传等多种方式。
  • 灵活的属性配置:通过 action、multiple、drag、accept 等属性可以自定义上传行为。
  • 丰富的事件处理:支持 on-success、on-error、on-progress、on-change 等事件,允许开发者处理上传过程中的各个阶段。
  • 自定义上传实现:通过 http-request 方法可以实现自定义上传逻辑,满足特殊需求。
  • 多样化的上传示例:如头像上传、照片墙、文件缩略图等,可以满足各种实际应用场景的需求。

# 4. 自定义头像上传

# 1. 自定义头像上传实现

Element-UI 上传组件默认情况下不提供头像上传成功后覆盖上传组件的位置的功能,可以通过自定义样式和插槽实现这一功能。

<template>
  <div id="app">
    <el-upload
      class="avatar-uploader"
      action="https://jsonplaceholder.typicode.com/posts/"
      :show-file-list="false"
      :on-success="handleAvatarSuccess"
      :on-error="handleAvatarError"
      :before-upload="beforeAvatarUpload"
      :on-progress="handleProgress"
      drag
    >
      <div v-if="uploading" class="progress-wrapper">
        <el-progress type="circle" :percentage="uploadPercentage"></el-progress>
        <div class="progress-text">{{ uploadPercentage }}%</div>
      </div>
      <div v-else-if="imageUrl" class="avatar-container">
        <img :src="imageUrl" class="avatar" />
      </div>
      <div v-else class="upload-placeholder">
        <i class="el-icon-plus avatar-uploader-icon"></i>
        <div class="el-upload__text">拖拽或点击上传</div>
      </div>
    </el-upload>
  </div>
</template>

<script>
export default {
  data() {
    return {
      imageUrl: '', // 用于存储上传成功后的图片URL
      uploading: false, // 上传状态
      uploadPercentage: 0 // 上传进度百分比
    };
  },
  methods: {
    // 上传成功后的处理函数
    handleAvatarSuccess(response, file) {
      this.uploading = false; // 结束上传状态
      this.imageUrl = response.url; // 使用服务器返回的URL
    },
    // 上传失败后的处理函数
    handleAvatarError(err, file) {
      this.uploading = false; // 结束上传状态
      this.$message.error('上传失败,请重试!');
    },
    // 上传前的检查函数,限制文件格式和大小
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
      const isLt500K = file.size / 1024 / 1024 < 0.5;

      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG 或 PNG 格式!');
      }
      if (!isLt500K) {
        this.$message.error('上传头像图片大小不能超过 500KB!');
      }
      this.uploading = true; // 开始上传状态
      this.uploadPercentage = 0; // 重置进度条
      return isJPG && isLt500K;
    },
    // 处理上传进度
    handleProgress(event, file, fileList) {
      this.uploadPercentage = Math.round((event.loaded / event.total) * 100);
    }
  }
};
</script>

<style scoped>
/* 上传组件的主要容器样式 */
.avatar-uploader {
  position: relative; /* 定位方式为相对定位,以便内部元素定位 */
  width: 178px; /* 固定宽度 */
  height: 178px; /* 固定高度 */
  border: 1px dashed #d9d9d9; /* 边框样式为虚线 */
  border-radius: 6px; /* 边框圆角 */
  cursor: pointer; /* 鼠标指针样式为手形 */
  overflow: hidden; /* 隐藏溢出内容 */
  display: flex; /* 使用弹性布局 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
}

/* 包含上传图片的容器样式 */
.avatar-container {
  width: 100%; /* 宽度为父元素的100% */
  height: 100%; /* 高度为父元素的100% */
  display: flex; /* 使用弹性布局 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
  border-radius: 6px; /* 边框圆角 */
  overflow: hidden; /* 隐藏溢出内容 */
}

/* 上传图标样式 */
.avatar-uploader-icon {
  font-size: 28px; /* 字体大小 */
  color: #8c939d; /* 字体颜色 */
}

/* 上传的图片样式 */
.avatar {
  max-width: 100%; /* 最大宽度为父元素的100% */
  max-height: 100%; /* 最大高度为父元素的100% */
  object-fit: contain; /* 保持图像的长宽比 */
}

/* 上传占位符样式 */
.upload-placeholder {
  display: flex; /* 使用弹性布局 */
  flex-direction: column; /* 垂直排列 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
  width: 100%; /* 宽度为父元素的100% */
  height: 100%; /* 高度为父元素的100% */
}

/* 进度条容器样式 */
.progress-wrapper {
  position: absolute; /* 定位方式为绝对定位 */
  top: 50%; /* 垂直居中 */
  left: 50%; /* 水平居中 */
  transform: translate(-50%, -50%); /* 移动到中心位置 */
  width: 80px; /* 固定宽度 */
  height: 80px; /* 固定高度 */
  display: flex; /* 使用弹性布局 */
  flex-direction: column; /* 垂直排列 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
}

/* 进度条文本样式 */
.progress-text {
  margin-top: 8px; /* 顶部外边距 */
  font-size: 14px; /* 字体大小 */
  color: #606266; /* 字体颜色 */
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  • 拖拽上传:通过 drag 属性启用拖拽上传功能。
  • 隐藏文件列表:通过 :show-file-list="false" 属性隐藏默认的文件列表。
  • 上传成功处理:通过 :on-success="handleAvatarSuccess" 处理上传成功后的回调,将返回的图片 URL 存储在 imageUrl 变量中。
  • 上传失败处理:通过 :on-error="handleAvatarError" 处理上传失败后的回调,显示错误信息。
  • 上传前检查:通过 :before-upload="beforeAvatarUpload" 处理上传前的检查,确保上传的文件格式和大小符合要求。
  • 进度条居中:使用绝对定位和 transform 属性确保进度条始终在上传容器的中心。
  • 上传状态处理:上传成功或失败后,uploading 状态设置为 false,隐藏进度条。
  • 进度条文本:添加了 progress-text 类,用于显示上传进度的百分比。
  • 自定义样式:通过 CSS 样式实现上传组件的位置覆盖功能,当 imageUrl 有值时显示上传的图片,否则显示上传图标和提示文字。
    • avatar-uploader:定义上传组件的主要容器样式,包括大小、边框、定位等。
    • avatar-container:包含上传图片的容器,设置为100%宽高以覆盖整个上传区域。
    • avatar-uploader-icon:上传图标的样式,包括字体大小和颜色。
    • avatar:上传的图片样式,设置为100%宽高以覆盖整个上传区域。
    • upload-placeholder:上传占位符样式,在上传组件为空时显示上传图标和提示文字,覆盖整个上传区域。
    • object-fit: contain:保持图像的长宽比,并确保图像完整地显示在容器内。
    • max-width 和 max-height:将上传的图片样式设置为 max-width: 100% 和 max-height: 100%,确保图片在容器内不被放大失真。

# 2. 封装 AvatarUploader 组件

AvatarUploader.vue

子组件(AvatarUploader.vue)通过 props 接收 父组件绑定的value值和action值,并在头像上传成功后通过 $emit 触发 父组件绑定在子组件上的input 事件,将新图片 URL 传递给父组件。

  • 父组件给子组件绑定v-model="avatarUrl"其实就等价于:value="avatarUrl" @input="avatarUrl= $event.target.value"。
  • 子组件可以使用props去接收,并且子组件去通过$emit 触发 父组件绑定在子组件上的input 事件的时候,父组件是不需要手动去编写input事件去完成数据的更新的,一切都是vue自动完成的。
<template>
  <div>
    <el-upload
      class="avatar-uploader"
      :action="action"
      :headers="headers"
      :show-file-list="false"
      :on-success="handleAvatarSuccess"
      :on-error="handleAvatarError"
      :before-upload="beforeAvatarUpload"
      :on-progress="handleProgress"
      drag
    >
      <div v-if="uploading" class="progress-wrapper">
        <el-progress type="circle" :percentage="uploadPercentage"></el-progress>
        <div class="progress-text">{{ uploadPercentage }}%</div>
      </div>
      <div v-else-if="imageUrl" class="avatar-container">
        <img :src="imageUrl" class="avatar" />
      </div>
      <div v-else class="upload-placeholder">
        <i class="el-icon-plus avatar-uploader-icon"></i>
        <div class="el-upload__text">拖拽或点击上传</div>
      </div>
    </el-upload>
  </div>
</template>

<script>
export default {
  name: 'AvatarUploader',
  props: {
    action: {
      type: String,
      required: true
    },
    headers: {
      type: Object,
      default: () => ({})
    },
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      imageUrl: this.value, // 用于存储上传成功后的图片URL
      uploading: false, // 上传状态
      uploadPercentage: 0 // 上传进度百分比
    };
  },
  watch: {
    value(newVal) {
      this.imageUrl = newVal;
    }
  },
  methods: {
    // 上传成功后的处理函数
    handleAvatarSuccess(response, file) {
      this.uploading = false; // 结束上传状态
      this.imageUrl = response.url; // 使用服务器返回的URL
      this.$emit('input', this.imageUrl);
    },
    // 上传失败后的处理函数
    handleAvatarError(err, file) {
      this.uploading = false; // 结束上传状态
      this.$message.error('上传失败,请重试!');
    },
    // 上传前的检查函数,限制文件格式和大小
    beforeAvatarUpload(file) {
      const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
      const isLt500K = file.size / 1024 / 1024 < 0.5;

      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG 或 PNG 格式!');
        return false;  // 验证失败时返回 false,避免进度条显示。
      }
      if (!isLt500K) {
        this.$message.error('上传头像图片大小不能超过 500KB!');
        return false;  
      }
      this.uploading = true; // 开始上传状态
      this.uploadPercentage = 0; // 重置进度条
      return isJPG && isLt500K;
    },
    // 处理上传进度
    handleProgress(event, file, fileList) {
      this.uploadPercentage = Math.round((event.loaded / event.total) * 100);
    }
  }
};
</script>

<style scoped>
/* 上传组件的主要容器样式 */
.avatar-uploader {
  position: relative; /* 定位方式为相对定位,以便内部元素定位 */
  width: 178px; /* 固定宽度 */
  height: 178px; /* 固定高度 */
  border: 1px dashed #d9d9d9; /* 边框样式为虚线 */
  border-radius: 6px; /* 边框圆角 */
  cursor: pointer; /* 鼠标指针样式为手形 */
  overflow: hidden; /* 隐藏溢出内容 */
  display: flex; /* 使用弹性布局 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
}

/* 包含上传图片的容器样式 */
.avatar-container {
  width: 100%; /* 宽度为父元素的100% */
  height: 100%; /* 高度为父元素的100% */
  display: flex; /* 使用弹性布局 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
  border-radius: 6px; /* 边框圆角 */
  overflow: hidden; /* 隐藏溢出内容 */
}

/* 上传图标样式 */
.avatar-uploader-icon {
  font-size: 28px; /* 字体大小 */
  color: #8c939d; /* 字体颜色 */
}

/* 上传的图片样式 */
.avatar {
  max-width: 100%; /* 最大宽度为父元素的100% */
  max-height: 100%; /* 最大高度为父元素的100% */
  object-fit: contain; /* 保持图像的长宽比 */
}

/* 上传占位符样式 */
.upload-placeholder {
  display: flex; /* 使用弹性布局 */
  flex-direction: column; /* 垂直排列 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
  width: 100%; /* 宽度为父元素的100% */
  height: 100%; /* 高度为父元素的100% */
}

/* 进度条容器样式 */
.progress-wrapper {
  position: absolute; /* 定位方式为绝对定位 */
  top: 50%; /* 垂直居中 */
  left: 50%; /* 水平居中 */
  transform: translate(-50%, -50%); /* 移动到中心位置 */
  width: 80px; /* 固定宽度 */
  height: 80px; /* 固定高度 */
  display: flex; /* 使用弹性布局 */
  flex-direction: column; /* 垂直排列 */
  align-items: center; /* 垂直居中对齐 */
  justify-content: center; /* 水平居中对齐 */
}

/* 进度条文本样式 */
.progress-text {
  margin-top: 8px; /* 顶部外边距 */
  font-size: 14px; /* 字体大小 */
  color: #606266; /* 字体颜色 */
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

# 3. 使用 AvatarUploader 组件

App.vue

使用封装的头像上传组件,我们需要传递服务器地址完成头像上传,还需要通过 v-model完成与子组件头像地址的双向绑定,这样父组件就可以将头像地址存到后端数据库了。

<template>
  <div id="app">
    <avatar-uploader
      :action="action"
      v-model="avatarUrl"
    ></avatar-uploader>
  </div>
</template>

<script>
import AvatarUploader from './components/AvatarUploader.vue';

export default {
  name: 'App',
  components: {
    AvatarUploader
  },
  data() {
    return {
      avatarUrl: '', // 存储头像的 URL
      action: 'https://jsonplaceholder.typicode.com/posts/' // 上传地址
    };
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

image-20240808085515966

  1. 组件封装:在 AvatarUploader.vue 中封装了上传头像的组件,支持 drag 和点击上传。通过 props 传递 action 和 headers 参数,使用 v-model 绑定上传后的图片 URL。
  2. 父组件使用:在 App.vue 中引入并使用 AvatarUploader 组件,通过 v-model 绑定 avatarUrl 变量,实时更新上传的图片 URL。

关键点总结

  • v-model 语法糖:在父组件中使用 v-model 实现数据的双向绑定。
  • 子组件 $emit('input', newValue):子组件通过 $emit 触发 input 事件,将新值传递给父组件。
  • 父组件更新:父组件自动监听 input 事件,并更新绑定的变量。
  • 数据流向:上传成功后,新的图片 URL 会从子组件传递到父组件,父组件的 avatarUrl 变量会更新,子组件通过 watch 监听 value 变化,实现数据同步。

这样封装后的组件可以在任何地方复用,并且通过 props 和 v-model 使其更加灵活和可配置。

# 5. Vue 中的 v-model 双向绑定机制

Vue 的 v-model 指令是用于在表单控件或组件上创建双向数据绑定的语法糖。它本质上是 :value 和 @input 这两个属性的简写。通过 v-model,我们可以更方便地进行双向数据绑定,使得父组件和子组件之间的数据同步变得更加简洁和高效。

# 1. v-model 的基本用法

在 Vue 中,使用 v-model 可以轻松地绑定输入框的值,例如:

<template>
  <input v-model="message">
</template>

<script>
export default {
  data() {
    return {
      message: '' // 绑定到输入框的变量
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. v-model 的本质

v-model 实际上是对以下两个属性的简写:

<template>
  <input :value="message" @input="message = $event.target.value">
</template>
1
2
3
  • :value:将 message 变量的值绑定到输入框的 value 属性。
  • @input:监听输入框的 input 事件,并在事件触发时更新 message 变量。

# 3. 自定义组件中的 v-model

在自定义组件中,我们可以通过 props 和 $emit 实现与 v-model 的兼容性。

子组件

<template>
  <input :value="value" @input="$emit('input', $event.target.value)">
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    }
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

父组件

<template>
  <custom-input v-model="message"></custom-input>
</template>

<script>
import CustomInput from './CustomInput.vue';

export default {
  components: {
    CustomInput
  },
  data() {
    return {
      message: '' // 绑定到子组件的变量
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 4. 双向数据绑定的详细实现

在自定义组件中,v-model 通过 props 接收绑定的值,并通过 $emit 触发 input 事件,将更新后的值传递回父组件。

  • 子组件 props:通过 props 接收父组件传递的值。
props: {
  value: {
    type: String,
    default: ''
  }
}
1
2
3
4
5
6
  • 子组件触发 input 事件:当输入框的值发生变化时,子组件通过 $emit('input', newValue) 触发 input 事件,将新值传递给父组件。
methods: {
  handleInput(event) {
    this.$emit('input', event.target.value);
  }
}
1
2
3
4
5
  • 父组件监听 input 事件:在父组件中,通过 v-model 实现对 input 事件的自动监听,当子组件触发 input 事件时,父组件会自动更新绑定的变量,不需要我们手动编写input事件。
<custom-input v-model="message"></custom-input>
1

等同于:

<custom-input :value="message" @input="message = $event"></custom-input>
1
  1. v-model 是 :value 和 @input 的语法糖:v-model 通过简化语法,实现数据的双向绑定。

  2. 子组件通过 props 接收 value:子组件使用 props 接收父组件传递的值,并通过 $emit 触发 input 事件,将新值传递回父组件。

  3. 父组件自动监听 input 事件:父组件使用 v-model 自动监听子组件触发的 input 事件,并更新绑定的变量。

编辑此页 (opens new window)
上次更新: 2024/12/28, 18:32:08
日期时间选择器(DateTimePicker)
评分(Rate)

← 日期时间选择器(DateTimePicker) 评分(Rate)→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式