目录

一、文件上传api 

二、封装组件

三、使用案例


一、文件上传api 

在src/api下新建file文件夹,并在file文件夹下新建index.ts和types.ts

// src/api/file/types.ts
/**
 * 文件API类型声明
 */
export interface FileInfo {
  name: string;
  url: string;
}
// src/api/file/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { FileInfo } from './types';

/**
 * 上传文件
 *
 * @param file
 */
export function uploadFileApi(file: File): AxiosPromise<FileInfo> {
  const formData = new FormData();
  formData.append('file', file);
  return request({
    url: '/api/v1/files',
    method: 'post',
    data: formData,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });
}

/**
 * 删除文件
 *
 * @param filePath 文件完整路径
 */
export function deleteFileApi(filePath?: string) {
  return request({
    url: '/api/v1/files',
    method: 'delete',
    params: { filePath: filePath }
  });
}

二、封装组件

单文件上传组件多文件上传组件

在src/components下新建Upload文件夹,并在Upload文件夹中新建SingleUpload.vue和MultiUpload.vue

<!--src/components/Upload/SingleUpload.vue-->
<template>
  <!-- 上传组件 -->
  <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 } from '@element-plus/icons-vue';
import {
  ElMessage,
  ElUpload,
  UploadRawFile,
  UploadRequestOptions
} from 'element-plus';
import { uploadFileApi } from '@/api/file';

const emit = defineEmits(['update:modelValue']);

const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
});

const imgUrl = computed<string | undefined>({
  get() {
    return props.modelValue;
  },
  set(val) {
    // imgUrl改变时触发修改父组件绑定的v-model的值
    emit('update:modelValue', val);
  }
});

/**
 * 自定义图片上传
 *
 * @param options
 */
async function uploadFile(options: UploadRequestOptions): Promise<any> {
  const { data: fileInfo } = await uploadFileApi(options.file);
  imgUrl.value = fileInfo.url;
}

/**
 * 限制用户上传文件的格式和大小
 */
function handleBeforeUpload(file: UploadRawFile) {
  if (file.size > 2 * 1048 * 1048) {
    ElMessage.warning('上传图片不能大于2M');
    return false;
  }
  return true;
}
</script>

<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;
}
</style>
<!--src/components/Upload/MultiUpload.vue-->
<!--
  多图上传组件
  @author: youlaitech
  @date 2022/11/20
-->

<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[]) => {
    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> {
  // 上传API调用
  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) {
  const filePath = removeFile.url;

  if (filePath) {
    deleteFileApi(filePath).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>

三、使用案例

在src/views/component下新建uploader.vue

<!--src/views/component/uploader.vue-->
<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>

Vue3后台管理系统(十)文件上传_ico