feat: 封装多图上传组件

Former-commit-id: d36f4fb6fb9e2c3da42518f6689a78ab2370af57
This commit is contained in:
haoxr
2022-11-21 00:03:16 +08:00
parent 6f001a7713
commit fe6669d813
6 changed files with 246 additions and 92 deletions

View File

@@ -1,11 +1,13 @@
import request from '@/utils/request'; import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FileInfo } from './types';
/** /**
* *
* *
* @param file * @param file
*/ */
export function uploadFile(file: File) { export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
return request({ return request({
@@ -21,12 +23,12 @@ export function uploadFile(file: File) {
/** /**
* *
* *
* @param path * @param fileName
*/ */
export function deleteFile(path?: string) { export function deleteFileApi(fileName?: string) {
return request({ return request({
url: '/api/v1/files', url: '/api/v1/files',
method: 'delete', method: 'delete',
params: { path: path } params: { fileName: fileName }
}); });
} }

7
src/api/file/types.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* 文件API类型声明
*/
export interface FileInfo {
name: string;
url: string;
}

View File

@@ -0,0 +1,154 @@
<!--
多图上传组件
@author: haoxr
@date 2022/11/20
@link https://element-plus.gitee.io/zh-CN/component/upload.html
-->
<template>
<el-upload
v-model:file-list="fileList"
list-type="picture-card"
:before-upload="handleBeforeUpload"
:http-request="handleUpload"
:on-remove="handleRemove"
:on-preview="handlePreview"
:limit="props.limit"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import {
ElMessage,
ElUpload,
UploadRawFile,
UploadRequestOptions,
UploadUserFile,
UploadFile,
UploadProps
} from 'element-plus';
import { uploadFileApi, deleteFileApi } from '@/api/file';
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
/**
* 文件路径集合
*/
modelValue: {
type: Array<string>,
default: [] as Array<string>
},
/**
* 文件上传数量限制
*/
limit: {
type: Number,
default: 5
}
});
const dialogImageUrl = ref('');
const dialogVisible = ref(false);
const fileList = ref([] as UploadUserFile[]);
watch(
() => props.modelValue,
(newVal: string[]) => {
console.log('newVal', newVal);
const filePaths = fileList.value.map(file => file.url);
// 监听modelValue文件集合值未变化时跳过赋值
if (
filePaths.length > 0 &&
filePaths.length === newVal.length &&
filePaths.every(x => newVal.some(y => y === x)) &&
newVal.every(y => filePaths.some(x => x === y))
) {
return;
}
fileList.value = newVal.map(filePath => {
return { url: filePath } as UploadUserFile;
});
},
{ immediate: true }
);
/**
* 自定义图片上传
*
* @param params
*/
async function handleUpload(options: UploadRequestOptions): Promise<any> {
const { data: fileInfo } = await uploadFileApi(options.file);
// 上传成功需手动替换文件路径为远程URL否则图片地址为预览地址 blob:http://
const fileIndex = fileList.value.findIndex(
file => file.uid == (options.file as any).uid
);
fileList.value.splice(fileIndex, 1, {
name: fileInfo.name,
url: fileInfo.url
} as UploadUserFile);
emit(
'update:modelValue',
fileList.value.map(file => file.url)
);
}
/**
* 删除图片
*/
function handleRemove(removeFile: UploadFile) {
console.log('removeFile', removeFile);
const fileUrl = removeFile.url;
if (fileUrl) {
let fileName = removeFile.name;
if (!fileName) {
// 文件名不存在从URL得到文件名
// 例: https://oss.youlai.tech/default/2022/11/20/{uuid}.jpg 得到文件名 2022/11/20/{uuid}.jpg
fileName = fileUrl.substring(fileUrl.indexOf('/', -3) + 1);
}
deleteFileApi(fileName).then(() => {
// 删除成功回调
emit(
'update:modelValue',
fileList.value.map(file => file.url)
);
});
}
}
/**
* 限制用户上传文件的格式和大小
*/
function handleBeforeUpload(file: UploadRawFile) {
if (file.size > 2 * 1048 * 1048) {
ElMessage.warning('上传图片不能大于2M');
return false;
}
return true;
}
/**
* 图片预览
*/
const handlePreview: UploadProps['onPreview'] = uploadFile => {
dialogImageUrl.value = uploadFile.url!;
dialogVisible.value = true;
};
</script>

View File

@@ -1,42 +1,28 @@
<template> <template>
<div> <!-- 上传组件 -->
<!-- 上传组件 --> <el-upload
<el-upload class="single-uploader"
ref="singleUploadRef" v-model="imgUrl"
action="" :show-file-list="false"
class="single-uploader" list-type="picture-card"
:show-file-list="false" :before-upload="handleBeforeUpload"
:before-upload="handleBeforeUpload" :http-request="uploadFile"
:http-request="uploadImage" >
> <img v-if="imgUrl" :src="imgUrl" class="single" />
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" /> <el-icon v-else class="single-uploader-icon"><Plus /></el-icon>
</el-upload>
<el-icon v-else class="single-uploader__plus">
<Plus />
</el-icon>
<!-- 删除图标 -->
<el-icon
v-if="props.showClose && imgUrl"
class="single-uploader__remove"
@click.stop="handleRemove(imgUrl)"
>
<Close />
</el-icon>
</el-upload>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { Plus, Close } from '@element-plus/icons-vue'; import { Plus } from '@element-plus/icons-vue';
import { import {
ElMessage, ElMessage,
ElUpload, ElUpload,
UploadRawFile, UploadRawFile,
UploadRequestOptions UploadRequestOptions
} from 'element-plus'; } from 'element-plus';
import { uploadFile, deleteFile } from '@/api/file'; import { uploadFileApi } from '@/api/file';
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
@@ -44,13 +30,6 @@ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
default: '' default: ''
},
/**
* 是否显示右上角的删除图片按钮
*/
showClose: {
type: Boolean,
default: false
} }
}); });
@@ -67,73 +46,52 @@ const imgUrl = computed<string | undefined>({
/** /**
* 自定义图片上传 * 自定义图片上传
* *
* @param params * @param options
*/ */
async function uploadImage(options: UploadRequestOptions): Promise<any> { async function uploadFile(options: UploadRequestOptions): Promise<any> {
const response = await uploadFile(options.file); const { data: fileInfo } = await uploadFileApi(options.file);
imgUrl.value = response.data; imgUrl.value = fileInfo.url;
} }
/** /**
* 删除图片 * 限制用户上传文件的格式和大小
*
* @param fileUrl
*/
function handleRemove(fileUrl?: string) {
if (fileUrl) {
deleteFile(fileUrl);
imgUrl.value = undefined; // 这里会触发imgUrl的computed的set方法
}
}
/**
* 在 before-upload 钩子中限制用户上传文件的格式和大小
*/ */
function handleBeforeUpload(file: UploadRawFile) { function handleBeforeUpload(file: UploadRawFile) {
// const isJPG = file.type === "image/jpeg"; if (file.size > 2 * 1048 * 1048) {
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
ElMessage.warning('上传图片不能大于2M'); ElMessage.warning('上传图片不能大于2M');
return false;
} }
return true; return true;
} }
</script> </script>
<style lang="scss" scoped> <style scoped>
.single-uploader { .single-uploader .single {
border: 1px dashed #d9d9d9; width: 178px;
height: 178px;
display: block;
}
</style>
<style>
.single-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
transition: var(--el-transition-duration-fast); transition: var(--el-transition-duration-fast);
}
.single-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.single-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center; text-align: center;
&:hover {
border-color: var(--el-color-primary);
}
&__image {
width: 146px;
height: 146px;
display: block;
}
&__plus {
width: 146px;
height: 157px;
font-size: 28px;
color: #8c939d;
text-align: center;
}
&__remove {
font-size: 12px;
color: #ff4d51 !important;
margin-top: 0px !important;
position: absolute;
right: 0;
top: 0;
color: #409eff;
}
} }
</style> </style>

View File

@@ -24,7 +24,7 @@ import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
// API 引用 // API 引用
import { uploadFile } from '@/api/file'; import { uploadFileApi } from '@/api/file';
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -46,8 +46,8 @@ const state = reactive({
uploadImage: { uploadImage: {
// 自定义图片上传 // 自定义图片上传
async customUpload(file: any, insertFn: any) { async customUpload(file: any, insertFn: any) {
uploadFile(file).then(response => { uploadFileApi(file).then(response => {
const url = response.data; const url = response.data.url;
insertFn(url); insertFn(url);
}); });
} }

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import SingleUpload from '@/components/Upload/SingleUpload.vue';
import MultiUpload from '@/components/Upload/MultiUpload.vue';
import { ElForm } from 'element-plus';
import { reactive, ref, toRefs } from 'vue';
const dataFormRef = ref(ElForm);
const state = reactive({
formData: {
picUrl:
'https://oss.youlai.tech/default/2022/11/20/18e206dae97b40329661537d1e433639.jpg',
picUrls: [
'https://oss.youlai.tech/default/2022/11/20/8af5567816094545b53e76b38ae9c974.webp',
'https://oss.youlai.tech/default/2022/11/20/13dbfd7feaf848c2acec2b21675eb9d3.webp'
]
}
});
const { formData } = toRefs(state);
</script>
<template>
<div class="app-container">
<el-form ref="dataFormRef" :model="formData">
<el-form-item label="单图上传">
<single-upload v-model="formData.picUrl"></single-upload>
</el-form-item>
<el-form-item label="多图上传">
<multi-upload v-model="formData.picUrls"></multi-upload>
</el-form-item>
</el-form>
</div>
</template>