antd+Vue 3实现table行内upload文件图片上传【超详细图解】

时间:2024-04-11 08:46:24

目录

一、背景

二、效果图

三、代码


一、背景

一名被组长逼着干前端的苦逼后端,在一个晴天霹雳的日子,被要求前端订单产品实现上传产品图片并立刻回显图片。

二、效果图

三、代码

<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>