基础环境
我在前面的文章中写明了当前具体环境版本,可以参考
Ant Design Pro 2(动态路由和菜单)
upload这个官方组件存在的问题
第一次看这个组件觉得效果很不错,结果一用才发现,开发体验极差,官方文档也是举例试,完全需要开发人员自己拓展,说好的组件化呢,还需要代工的组件化我还要你干嘛,对比下就项目用到的组件 bootstrap fileinput ,心累,因为框架迭代就是要有人去肝....
算了,不吐槽,处理现有的问题:
官方提供的案例中有图片弹窗预览效果,但是上传视频等非图片文件后,无法点击预览按钮,事件也是无法触发,这,我编辑查看详情页面时要怎么展示??
组件页面代码
<template>
<!-- 主视图 -->
<div class="upload-view">
<!-- 上传组件 -->
<a-upload
list-type="picture-card"
:accept="accept"
:multiple="multiple"
:disabled="disabled"
:showUploadList="showUploadList"
:fileList="fileList"
:beforeUpload="beforeUpload"
:customRequest="customRequest"
:remove="remove"
@preview="handlePreview"
@change="handleChange"
<div v-if="fileList.length < fileNumber">
<a-icon type="plus" />
<div class="ant-upload-text">
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img v-if="fileType === 'img'" alt="example" style="width: 100%" :src="previewImage" />
<video v-if="fileType === 'video'" width="100%" controls type="video" id="video">
<source :src="videoSrc" type="video/mp4" />
<object :data="videoSrc" width="100%">
<embed :src="videoSrc" width="100%" />
</object>
您的浏览器不支持video标签哦,请联系管理员
</video>
</a-modal>
</template>
<script>
import { uploadCreate, uploadDelete } from '@/api/upload'
function getBase64 (file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = error => reject(error)
export default {
name: 'AntdUploadImg',
data () {
return {
previewVisible: false,
previewImage: '',
fileType: 'img',
videoSrc: '',
showUploadList: {
showRemoveIcon: true,
showPreviewIcon: true
props: {
// =============================== 原生属性 - a-upload 自带属性扩展 ========
// 接受上传的文件类型
// 参考地址:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers
// 音频:'audio/*'
// 视频:'video/*'
// 图片:'image/*'
// 特殊匹配自行传入正则
accept: {
type: String,
default: () => undefined
// 已经上传的文件列表(受控)
// 文件案例:
// [{
// uid: '必带,文件唯一标识',
// name: '必带,文件名',
// // uploading(上传中)、done(上传成功)、error(上传失败)、removed(移除,点击组件自带的删除按钮会被设置为移除状态,通常只需要前三种状态)
// status: '必带,上传状态',
// dupid: '可选,防止重复文件标识(file.lastModified)',
// upid: '可选,本轮上传唯一标识,提交服务器时可剔除',
// 如果需要什么其他字段或辅助字段,可以自行添加,或者通过拦截 beforeUploadPro 拿到 fileJson 自行附带
// }]
fileList: {
type: Array,
default: () => []
// 上传时携带的参数
// product_id: 0,//主键id
// file_type_id: 60,//类型
// tables: 6,//表标识
// table_name: 'bar_id'//上传存储文件夹
uploadParameter: {
type: Array,
default: () => []
// 是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件。
multiple: {
type: Boolean,
default: () => true
// 是否禁用
disabled: {
type: Boolean,
default: () => false
// 点击移除文件时的回调,返回值为 false 时不移除
// 类型:(file) => boolean || Promise
removePro: {
type: Function,
default: undefined
// =============================== 公用检测 - 文件数量限制 ========
// 上传文件数量限制:0 -> 不限制,随便传
fileNumber: {
type: Number,
default: () => 0
// =============================== 公用检测 - 文件大小 ========
// 文件大小检测模式(单位 kb):
// 0 -> 关闭
// 1 -> 小于
// 2 -> 大于
// 3 -> 等于
// 11 -> 小于或等于
// 22 -> 大于或等于
kbCompareMode: {
type: Number,
default: () => 0
// 文件大小(单位 kb)
kbCompareSize: {
type: Number,
default: () => 0
methods: {
// 暴露方法获取filelist
getNewFileLIst () {
return this.fileList
// 暴露方法初初始化文件列表
customSetFileList (record) {
// 清空,在延迟赋值
this.fileList = []
setTimeout(() => {
this.fileList = record
}, 20)
// 自定义上传
customRequest (record) {
this.uploadParameter = record
// 找到对应的上传文件对象
const { uploadParameter } = this
const { fileList } = this
return new Promise((resolve, reject) => {
if (fileList.length > 0) {
const formData = new FormData()
// eslint-disable-next-line camelcase
let is_file = 0
fileList.forEach(file => {
if (file.originFileObj) {
formData.append('FileModel[file]', file.originFileObj)
// eslint-disable-next-line camelcase
is_file = 1
// eslint-disable-next-line camelcase
if (is_file) {
formData.append('product_id', uploadParameter.product_id)
formData.append('file_type_id', uploadParameter.file_type_id)
formData.append('tables', uploadParameter.tables)
formData.append('table_name', uploadParameter.table_name)
return uploadCreate(formData).then(res => {
resolve()
}).catch(() => {
// 不允许上传
// eslint-disable-next-line prefer-promise-reject-errors
reject('上传失败了')
} else {
resolve()
} else {
resolve()
// 准备上传
beforeUpload (file, fileList) {
// console.log('file', file)
// 文件基本信息获取
if (this.isImage(file.name)) {
this.fileType = 'img'
} else if (this.isVideo(file.name)) {
this.fileType = 'video'
// 获取文件大小(单位:kb)
const fileSize = file.size / 1024 / 1024
// eslint-disable-next-line camelcase
let is_error = 0
if (fileSize > this.kbCompareSize) {
this.$message.error('文件必须小于或者等于 ' + this.kbCompareSize + 'MB!')
// eslint-disable-next-line camelcase
is_error = 1
} else if (this.accept !== undefined) {
// 还是要拦截判断下文件后缀,选择的时候用户可能恶意选择全部类型
if (file.type) {
const types = this.accept.split(',')
if (types.indexOf(file.type) === -1) {
this.$message.error('文件类型不合法')
} else {
const suffix = this.fileExtension(file.name)
// 当前实验出excel是识别不出来的,所以这里应该要单独验证了
if (suffix === 'xlsx' && this.accept !== 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
this.$message.error('文件类型不合法')
// eslint-disable-next-line camelcase
if (is_error) {
setTimeout(() => {
const index = this.fileList.indexOf(file)
const newFileList = this.fileList.slice()
newFileList.splice(index, 1)
this.fileList = newFileList
} else {
this.fileList = [...this.fileList, file]
return false
// 点击移除文件时的回调
remove (file) {
// console.log(file)
// 删除文件
const index = this.fileList.indexOf(file)
this.fileList.splice(index, 1)
if (file.url) {
return uploadDelete(file.uid)
.then(res => {
this.$message.success('操作成功')
// 关闭预览弹窗
handleCancel () {
this.previewVisible = false
// 点击预览图标时
async handlePreview (file) {
// console.log(file)
if (file.url) {
// 文件基本信息获取
if (this.isImage(file.name)) {
this.fileType = 'img'
} else if (this.isVideo(file.name)) {
this.fileType = 'video'
if (!file.url && !file.preview && this.fileType === 'img') {
file.preview = await getBase64(file.originFileObj)
if (this.fileType === 'img') {
this.previewImage = file.url || file.preview
this.previewVisible = true
} else if (this.fileType === 'video') {
if (file.url) {
this.videoSrc = file.url
this.previewVisible = true
} else {
const current = file.originFileObj
const fileReader = new FileReader()
fileReader.readAsDataURL(current)
const that = this
fileReader.onload = function (e) {
that.videoSrc = e.currentTarget.result
that.previewVisible = true
// 选择文件后事件
handleChange ({ fileList }) {
this.fileList = fileList
// 预览图片特殊处理,上传非图片文件时,修改样式
setTimeout(() => {
const classActions = document.getElementsByClassName('ant-upload-list-item-actions')
classActions.forEach(ca => {
ca.children[0].style.opacity = '1'
ca.children[0].style.pointerEvents = 'initial'
// console.log(classActions)
}, 30)
// 是否为图片
isImage (filePath) {
// 图片后缀
const types = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff']
// 文件后缀
const type = this.fileExtension(filePath)
// 是否包含
return types.indexOf(type) !== -1
// 是否为视频
isVideo (filePath) {
// 图片后缀
const types = ['avi', 'wmv', 'mpg', 'mpeg', 'mov', 'rm', 'ram', 'swf', 'flv', 'mp4', 'mp3', 'wma', 'avi', 'rm', 'rmvb', 'flv', 'mpg', 'mkv']
// 文件后缀
const type = this.fileExtension(filePath)
// 是否包含
return types.indexOf(type) !== -1
// 获取文件后缀类型
fileExtension (filePath) {
// 获取最后一个.的位置
const index = filePath.lastIndexOf('.')
// 获取后缀
const type = filePath.substr(index + 1)
// 返回类型
return type.toLowerCase()
watch: {
// 预览弹窗关闭,监听下,如果是视频,要把视频停止
previewVisible: function (val) {
if (this.previewVisible === false && this.fileType === 'video') {
const thisVideo = document.getElementById('video')
thisVideo.pause()
</script>
<style scoped>
/* you can make up upload button and sample style by using stylesheets */
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
.upload-view .ant-upload-list-item-list-type-picture-card .ant-upload-list-item-actions a {
pointer-events: initial;
opacity: 1;
</style>
新增页面调用
<antd-upload-img
ref="videoUploadRef"
:accept="'video/mp4'"
:multiple="false"
:fileNumber="1"
:uploadParameter="videoParameter"
:kbCompareSize="100"
></antd-upload-img>
注入声明,初始化变量
videoParameter变量是我放上传时的一些额外参数,我这边上传走的是独立的接口,所以要先走业务表新增,返回标识,才会继续走上传接口
import AntdUploadImg from '@/components/UploadFile/AntdUploadImg'
components: {
AntdUploadImg
data () {
return {
videoParameter: {
product_id: this.bar_id,
file_type_id: 64,
tables: 6,
table_name: 'b_bar'
调用上传接口,customRequest是组件暴露出来触发上传的方法
this.$refs.videoUploadRef.customRequest(this.videoParameter)
编辑页面调用
编辑页面跟新增页面差不多,因为走的是独立上传接口,只要根据暴露的组件方法去初始化fileList就可以了
this.videoFileList.push({
uid: item.files_middle_id,
name: item.photos.name,
status: 'done',
url: item.photos.url
this.$refs.videoUploadRef.customSetFileList(this.videoFileList)
至于文件删除的话,因为我们走的都是独立附件处理接口,所以就直接放在组件中了,如果需要触发删除事件,可以自己写$emit
remove (file) {
// console.log(file)
// 删除文件
const index = this.fileList.indexOf(file)
this.fileList.splice(index, 1)
if (file.url) {
return uploadDelete(file.uid)
.then(res => {
this.$message.success('操作成功')
视频图片相关效果图