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 { AxiosPromise } from 'axios';
import { FileInfo } from './types';
/**
*
*
* @param file
*/
export function uploadFile(file: File) {
export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
const formData = new FormData();
formData.append('file', file);
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({
url: '/api/v1/files',
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>
<div>
<!-- 上传组件 -->
<el-upload
ref="singleUploadRef"
action=""
class="single-uploader"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:http-request="uploadImage"
>
<img v-if="imgUrl" :src="imgUrl" class="single-uploader__image" />
<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>
<!-- 上传组件 -->
<el-upload
class="single-uploader"
v-model="imgUrl"
:show-file-list="false"
list-type="picture-card"
:before-upload="handleBeforeUpload"
:http-request="uploadFile"
>
<img v-if="imgUrl" :src="imgUrl" class="single" />
<el-icon v-else class="single-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Plus, Close } from '@element-plus/icons-vue';
import { Plus } from '@element-plus/icons-vue';
import {
ElMessage,
ElUpload,
UploadRawFile,
UploadRequestOptions
} from 'element-plus';
import { uploadFile, deleteFile } from '@/api/file';
import { uploadFileApi } from '@/api/file';
const emit = defineEmits(['update:modelValue']);
@@ -44,13 +30,6 @@ const props = defineProps({
modelValue: {
type: String,
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> {
const response = await uploadFile(options.file);
imgUrl.value = response.data;
async function uploadFile(options: UploadRequestOptions): Promise<any> {
const { data: fileInfo } = await uploadFileApi(options.file);
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) {
// const isJPG = file.type === "image/jpeg";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
if (file.size > 2 * 1048 * 1048) {
ElMessage.warning('上传图片不能大于2M');
return false;
}
return true;
}
</script>
<style lang="scss" scoped>
.single-uploader {
border: 1px dashed #d9d9d9;
<style scoped>
.single-uploader .single {
width: 178px;
height: 178px;
display: block;
}
</style>
<style>
.single-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
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;
&: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>

View File

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