目录
一、背景
二、效果图
三、代码
一、背景
一名被组长逼着干前端的苦逼后端,在一个晴天霹雳的日子,被要求前端订单产品实现上传产品图片并立刻回显图片。
二、效果图
三、代码
<template>
<a-table :dataSource="dataSource" :columns="columns">
/** 我这里只举例上传图片的插槽 */
<template #base64Url="{ record }">
<a-upload v-model:file-list="record.fileList" name="file" list-type="picture-card" class="product-upload" :show-upload-list="false" action="" @change="
(file) => {
return handleChange(file, record);
}
" :customRequest="
(file) => {
return requestUploadImg(file, record);
}
" accept="image/png, image/jpeg, image/jpg" :before-upload="beforeUpload">
<img v-if="record.base64Url" :src="record.base64Url" />
<div v-else>
<loading-outlined v-if="record.loading" class="ant-upload-icon" />
<div class="ant-upload-text" v-if="record.loading">上传中</div>
<cloud-upload-outlined v-else class="ant-upload-icon" />
<div class="ant-upload-text" v-if="!record.loading">支持上传 .jpg .jpeg .png 且小于 2MB 的图片</div>
</div>
</a-upload>
</template>
</a-table>
</template>
<script lang="ts">
import { LoadingOutlined, CloudUploadOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { defineComponent, ref } from 'vue';
interface FileItem {
uid: string;
name?: string;
status?: string;
response?: string;
url?: string;
type?: string;
size: number;
originFileObj: any;
}
interface FileInfo {
file: FileItem;
fileList: FileItem[];
}
function getBase64(img: Blob, callback: (base64Url: string) => void) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result as string));
reader.readAsDataURL(img);
}
export default defineComponent({
components: {
LoadingOutlined,
CloudUploadOutlined,
},
setup() {
//这个只要file的状态发生改变就会触发
const handleChange = (info: FileInfo, record) => {
if (info.file.status === 'uploading') {
record.loading = true;
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, (base64Url: string) => {
record.base64Url = base64Url;
record.loading = false;
});
message.success('upload success');
}
if (info.file.status === 'error') {
record.loading = false;
message.error('upload error');
}
};
//这个是上传图片之前的校验,限制图片的格式和大小。也可以在upload标签中使用accept和size设定用户上传时就禁止点击不符合条件的文件
const beforeUpload = (file: FileItem) => {
const isJpgOrPng =
file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
};
//覆盖默认的上传行为,自定义自己的上传实现
const requestUploadImg = async (info, record) => {
requestUploadImgApi({ file: info.file })
.then((res) => {
//必须转为blob格式(二进制文件),否则handleChange方法中接收的info.file中没有originFileObj
const urlData = URL.createObjectURL(info.file);
//必须调用这个方法,否则上传组件的状态将一直是loading状态,传入的res, info.file, record位置不允许改变,且res必须是包含code、data、status、message等信息的responce,而不是data里的数据,否则会一直是error状态或者loading状态
info.onSuccess(res, info.file, record);
})
.catch((err) => {
info.onError();
});
};
return {
dataSource: [
{
key: '1',
name: '产品1',
desc: '产品描述1',
base64Url: '',
fileList: [],
loading: false,
},
{
key: '2',
name: '产品2',
desc: '产品描述2',
base64Url: '',
fileList: [],
loading: false,
},
],
columns: [
{
title: '产品名称',
dataIndex: 'name',
key: 'name',
},
{
title: '产品描述',
dataIndex: 'desc',
key: 'desc',
},
{
title: '产品图片',
dataIndex: 'base64Url',
key: 'base64Url',
slots: { customRender: 'base64Url' }, //这个表示插槽,在html结构中可以自定义样式
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
slots: { customRender: 'operation' },
},
],
handleChange,
beforeUpload,
requestUploadImg,
};
},
});
</script>
<style lang="less">
.product-upload > .ant-upload {
width: 204px;
height: 125px;
}
.ant-upload-icon {
font-size: 30px;
opacity: 0.5;
}
</style>