vue3使用ant上传组件各种情况
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
说明:上传是将信息(文件、图片、视频等)通过上传工具发布到远程服务器上的过程。
引入
1、引入文件夹UFileUpload
2、裁剪功能需要安装插件 vue-cropper
版本"vue-cropper": “^1.1.1”
主要功能
最大尺寸以及限制上传数量、文件样式、大文件上传视频、裁剪尺寸、文案配置、按钮配置可查看、删除、下载、禁用和回显查看
主要代码
<template>
<div class="clearfix">
<!-- 视频预览 -->
<div class="video-wrapper" v-if="reviewUrl">
<video :src="reviewUrl" width="320" height="200" controls>
<source :src="reviewUrl" type="video/mp4" />
<source :src="reviewUrl" type="video/ogg" />
<source :src="reviewUrl" type="video/webm" />
<object :data="reviewUrl" width="320" height="240">
<embed :src="reviewUrl" width="320" height="240" />
</object>
</video>
<DeleteOutlined v-if="!disabled" @click="deleteHandle" class="delete_icon" />
</div>
<Upload
v-else
v-model:file-list="localFileList"
:action="action"
:customRequest="crop || largeVideo ? () => {} : undefined"
:headers="headers"
:accept="accept"
:list-type="listType"
@preview="handlePreview"
:maxCount="maxCount"
@change="handleChange"
@remove="removeChange"
@download="handleDownload"
:before-upload="beforeUpload"
:multiple="multiple"
:showUploadList="{
showDownloadIcon: downLoad,
showPreviewIcon: preview,
showRemoveIcon: !disabled && remove,
}"
:disabled="disabled"
>
<template v-if="!disabled">
<!-- 图片上传卡片 -->
<template v-if="listType === ListTypeEnum.PICTURE_CARD">
<div v-if="!maxCount || localFileList.length < maxCount">
<PlusOutlined />
<p>{{ uploadText }}</p>
</div>
</template>
<a-button v-else>
<upload-outlined />
{{ uploadText }}
</a-button>
</template>
</Upload>
<!-- 信息描述 -->
<p v-if="extraInfo">{{ extraInfo }}</p>
<!-- 预览弹窗 -->
<Modal :visible="previewVisible" :footer="null" @cancel="previewVisible = false">
<img alt="无法查看" style="width: 100%" :src="previewImage" />
</Modal>
<!-- 裁剪弹窗 -->
<imageCut
ref="imageCutRef"
:autoCropWidth="autoCropWidth"
:autoCropHeight="autoCropHeight"
@change="cropChange"
:action="action"
:headers="headers"
@cancel="localFileList.pop()"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import type { Ref } from 'vue';
import { Upload, Modal, message } from 'ant-design-vue';
import { PlusOutlined, UploadOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { ListTypeEnum, UploadResultStatus } from './typing';
import type { UploadFile, UploadChangeParam } from 'ant-design-vue';
import { useALinkToDownload, getBase64, simplifyFileList } from './utils';
import { useUserStoreWithOut } from '/@/store/modules/user';
import imageCut from './imageCut.vue';
import { object } from 'vue-types';
const props = defineProps({
// 图片地址
fileList: {
type: Array,
defalut: [],
require: true,
},
// 最多数量
maxCount: {
type: Number,
require: false,
},
// 最大尺寸
maxSize: {
type: Number,
default: 20,
},
// 接受上传的文件类型
accept: String,
// 上传样式
listType: {
type: String,
default: 'text',
},
// 上传地址
action: {
type: String,
require: true,
default: '',
},
// 请求头
headers: {
type: Object,
default() {
return { 'zj-unicom-token': useUserStoreWithOut().getToken };
},
},
// 是否可以预览
preview: {
type: Boolean,
default: true,
},
// 是否可以下载
downLoad: {
type: Boolean,
default: true,
},
// 是否可以删除
remove: {
type: Boolean,
default: true,
},
// 是否禁用(查看)
disabled: {
type: Boolean,
default: false,
},
// 是否多选
multiple: {
type: Boolean,
default: false,
},
// 上传说件说明
extraInfo: {
type: String,
default: '',
},
// 上传文案
uploadText: {
type: String,
default: '上传',
},
// 是否裁剪
crop: {
type: Boolean,
default: false,
},
// 剪切宽度
autoCropWidth: {
type: Number,
default: 300,
},
// 剪切高度
autoCropHeight: {
type: Number,
default: 200,
},
// 是否展示裁剪预览图
cropPreview: {
type: Boolean,
default: true,
},
// 是否大文件上传视频
largeVideo: {
type: Boolean,
default: false,
},
// 大文件上传地址
largeAction: {
type: String,
default: '',
},
});
const localFileList: Ref<UploadFile[]> = ref([]);
const ERRORTEXT = '网络异常';
// 回显照片
watch(
() => props.fileList,
(val: any) => {
if (val && val.length && !localFileList.value.length) {
localFileList.value.push(...val);
}
},
{ immediate: true },
);
const emit = defineEmits(['update:fileList', 'change']);
// 判断尺寸大小
const beforeUpload = (file) => {
const isLtM = file.size / 1024 / 1024 < props.maxSize;
if (!isLtM) {
message.error(`上传内容必须小于${props.maxSize}M`);
}
let videoType = true;
if (props.largeVideo && !props.accept) {
videoType = file.type.includes('video');
if (!videoType) {
message.error('该文件不符合类型!');
}
}
return isLtM && videoType;
};
// 裁剪组件
const imageCutRef = ref();
// file的change事件
const handleChange = ({ file, fileList }: UploadChangeParam) => {
if (!file.status) {
// beforeUpload返回false,不生效
// 所以不符合尺寸和类型的change时,没有statue就删除
fileList.pop();
}
emit('update:fileList', [fileList]);
if (props.crop) {
file.originFileObj &&
getBase64(file.originFileObj, (imageUrl) => {
imageCutRef.value.showEdit({ img: imageUrl }, file.name);
});
} else if (props.largeVideo) {
largeVideoHandle(file);
} else {
uplaodHandle({ file, fileList });
}
};
// 普通上传
const uplaodHandle = ({ file, fileList }) => {
if (file.status === UploadResultStatus.DONE) {
if (file.response.code === 200) {
message.success('上传成功');
} else {
fileList.pop();
message.error(`${file.response.msg || file.response.message || ERRORTEXT}`);
}
const formatedFileList = simplifyFileList(fileList as UploadFile[]);
emit('update:fileList', formatedFileList);
emit('change', { fileList: formatedFileList });
}
};
// 视频地址
const reviewUrl = ref('');
// 视频上传
const largeVideoHandle = (file) => {
const params = `?fileName=${encodeURIComponent(file.name)}`;
fetch(props.largeAction + params, {
headers: props.headers,
})
.then((response) => response.json())
.then((res) => {
if (res.code === 200) {
createUploadHttp(res.data, file.originFileObj);
} else {
if (!props.action) {
message.error(res.msg || res.message || ERRORTEXT);
return;
}
videoOldUpdate(file);
}
});
};
// 上传的老接口
const videoOldUpdate = (file) => {
let formData = new FormData();
formData.append('file', file.originFileObj, file.name || 'file.png');
fetch(props.action || '', {
method: 'POST', // 设置请求方法为POST
headers: props.headers,
body: formData,
})
.then((response) => response.json())
.then((res) => {
if (res.code === 200) {
reviewUrl.value = res.data;
const temp = { url: reviewUrl.value, name: file.name };
emit('change', { fileList: temp });
localFileList.value.pop();
message.success('上传成功');
} else {
message.error(res.msg || res.message || ERRORTEXT);
}
});
};
// 视频上传
const createUploadHttp = (url, file) => {
let http = new XMLHttpRequest();
http.onload = () => {
if (http.status === 200) {
reviewUrl.value = url.split('?')[0];
const temp = { url: reviewUrl.value, name: file.name };
emit('change', { fileList: temp });
localFileList.value.pop();
message.success('上传成功');
} else {
message.warning('视频上传失败');
return false;
}
};
http.open('PUT', url, true);
http.send(file);
};
// 剪切回调
const cropChange = (data, name) => {
localFileList.value.pop();
localFileList.value.push({
url: data,
name,
type: 'image/png',
status: UploadResultStatus.DONE,
uid: Math.random().toString(),
});
const formatedFileList = simplifyFileList(localFileList.value);
emit('update:fileList', formatedFileList);
emit('change', { fileList: formatedFileList });
};
// 下载
const handleDownload = (file) => {
const url = file.url || file.response?.data?.url || file?.response?.data;
useALinkToDownload(url, file.name);
};
// 预览
const previewVisible = ref<boolean>(false);
const previewImage = ref<string | undefined>('');
const handlePreview = (file: any) => {
console.log('file: ', file);
const url = file.url || file.response?.data?.url || file?.response?.data;
// if (file.type?.includes('image') || ) {
previewImage.value = url;
previewVisible.value = true;
// }
};
// 移除图片
const deleteHandle = () => {
reviewUrl.value = '';
emit('change', null);
};
const removeChange = (file) => {
localFileList.value = localFileList.value.filter((item) => {
return item !== file;
});
const formatedFileList = simplifyFileList(localFileList.value);
emit('update:fileList', formatedFileList);
emit('change', { fileList: formatedFileList });
};
</script>
<style lang="less" scoped>
.delete_icon {
cursor: pointer;
margin-left: 10px;
height: 30px;
width: 30px;
font-size: 20px;
}
</style>
裁剪组件代码
<template>
<Modal
title="裁剪"
:visible="visible"
:confirm-loading="confirmLoading"
@ok="handleOk()"
@cancel="handleCancel"
>
<div class="cropper-content">
<div>
<div class="cropper">
<vue-cropper
ref="cropperRef"
:img="option.img"
:outputSize="option.outputSize"
:outputType="option.outputType"
:info="option.info"
:canScale="option.canScale"
:autoCrop="option.autoCrop"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:full="option.full"
:fixedBox="option.fixedBox"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:original="option.original"
:centerBox="option.centerBox"
:height="option.height"
:infoTrue="option.infoTrue"
:maxImgSize="option.maxImgSize"
:enlarge="option.enlarge"
:mode="option.mode"
@real-time="realTime"
/>
</div>
<!--底部操作工具按钮-->
<div class="footer-btn">
<div class="scope-btn">
<label class="btn" for="uploads">更换图片</label>
<input
type="file"
id="uploads"
style="position: absolute; clip: rect(0 0 0 0)"
accept="image/png, image/jpeg, image/gif, image/jpg"
@change="selectImg($event)"
/>
<a-button type="dashed" @click="changeScale(1)" class="mr">+ 放大</a-button>
<a-button type="dashed" @click="changeScale(-1)" class="mr">- 缩小</a-button>
<a-button type="dashed" @click="rotateLeft" class="mr">↺ 左旋转</a-button>
<a-button type="dashed" @click="rotateRight" class="mr">↺ 右旋转</a-button>
</div>
</div>
</div>
<!--预览效果图-->
<div class="show-preview" v-if="props.cropPreview">
<div :style="previews.div" class="preview">
<img :src="previews.url" :style="previews.img" />
</div>
</div>
</div>
</Modal>
</template>
<script lang="ts" setup>
import { VueCropper } from 'vue-cropper';
import 'vue-cropper/dist/index.css';
import { Modal, message } from 'ant-design-vue';
import { ref } from 'vue';
const emit = defineEmits(['change', 'cancel']); // 数组形式定义
const props = defineProps({
autoCropWidth: {
// 默认生成截图框宽度
type: Number,
default: 300,
},
autoCropHeight: {
// 默认生成截图框高度
type: Number,
default: 200,
},
// 是否直接展示预览图
cropPreview: {
type: Boolean,
default: true,
},
// 上传地址
action: {
type: String,
require: true,
},
headers: {
type: Object,
},
});
//封面原图
const originUrl = ref('');
const confirmLoading = ref(false);
const visible = ref(false);
const previews = ref({
div: '',
img: '',
url: '',
});
const option = ref({
img: '', //裁剪图片的地址
outputSize: 1, //裁剪生成图片的质量(可选0.1 - 1)
outputType: 'png', //裁剪生成图片的格式(jpeg || png || webp)
info: true, //图片大小信息
canScale: true, //图片是否允许滚轮缩放
autoCrop: true, //是否默认生成截图框
autoCropWidth: props.autoCropWidth, //默认生成截图框宽度
autoCropHeight: props.autoCropHeight, //默认生成截图框高度
fixed: true, //是否开启截图框宽高固定比例
fixedNumber: [props.autoCropWidth, props.autoCropHeight], //截图框的宽高比例
full: false, //false按原比例裁切图片,不失真
fixedBox: true, //固定截图框大小,不允许改变
canMove: false, //上传图片是否可以移动
canMoveBox: true, //截图框能否拖动
original: false, //上传图片按照原始比例渲染
centerBox: false, //截图框是否被限制在图片里面
height: true, //是否按照设备的dpr 输出等比例图片
infoTrue: false, //true为展示真实输出图片宽高,false展示看到的截图框宽高
maxImgSize: 3000, //限制图片最大宽度和高度
enlarge: 2, //图片根据截图框输出比例倍数
mode: 'contain', //图片默认渲染方式
});
const cropperRef = ref();
//初始化函数
//图片缩放
const changeScale = (num) => {
num = num || 1;
cropperRef.value.changeScale(num);
};
//向左旋转
const rotateLeft = () => {
cropperRef.value.rotateLeft();
};
//向右旋转
const rotateRight = () => {
cropperRef.value.rotateRight();
};
//选择图片
const selectImg = (e) => {
let file = e.target.files[0];
if (!/\.(jpg|jpeg|png|JPG|PNG|JPEG)$/.test(e.target.value)) {
message.error('图片类型要求:jpeg、jpg、png');
return false;
}
//转化为blob
let reader = new FileReader();
reader.onload = (e: any) => {
let data;
if (typeof e.target.result === 'object') {
data = window.URL.createObjectURL(new Blob([e.target.result]));
} else {
data = e.target.result;
}
option.value.img = data;
};
//转化为base64
reader.readAsDataURL(file);
};
//实时预览函数
const realTime = (data) => {
previews.value = data;
};
//上传图片
const handleOk = () => {
//获取截图的blob数据
cropperRef.value.getCropBlob(async (data) => {
let formData = new FormData();
formData.append('file', data, fileName.value || 'file.png');
confirmLoading.value = true;
fetch(props.action || '', {
method: 'POST', // 设置请求方法为POST
headers: props.headers,
body: formData,
})
.then((response) => response.json())
.then((res) => {
confirmLoading.value = false;
if (res.code === 200) {
emit('change', res.data, fileName.value);
message.success('上传成功');
visible.value = false;
} else {
message.error(res.msg || res.message || '网络异常');
}
});
});
};
const fileName = ref('');
const showEdit = async (record, name) => {
visible.value = true;
option.value = Object.assign(option.value, record);
fileName.value = name;
};
const handleCancel = () => {
originUrl.value = '';
visible.value = false;
emit('cancel');
};
defineExpose({
showEdit,
});
</script>
<style scoped lang="scss">
.cropper-content {
.cropper {
width: auto;
height: 300px;
}
.show-preview {
flex: 1;
display: flex;
justify-content: center;
margin-top: 20px;
.preview {
overflow: hidden;
background: #ebebeb;
}
}
}
.footer-btn {
margin-top: 30px;
display: flex;
justify-content: flex-start;
.scope-btn {
display: flex;
justify-content: space-between;
padding-right: 10px;
}
.mr {
margin-right: 10px;
}
.upload-btn {
flex: 1;
display: flex;
justify-content: center;
}
.btn {
cursor: pointer;
text-align: center;
padding: 0 15px;
line-height: 32px;
font-size: 14px;
border-radius: 3px;
color: #fff;
background-color: #2165f7;
margin-right: 10px;
}
}
</style>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献1条内容
所有评论(0)