前端上传图片和附件


需求是上传多张图片以及附件,支持复制粘贴上传,鼠标拖拽上传等

一、一次上传多张或单张(调用多次upload方法)

1.j-image-upload

 <j-image-upload v-model="model.projectsPic" :number="10" :fileType="['png', 'jpg', 'jpeg']" :fileSize="2" multiple />

本来使用的是j-image-upload组件,但是发现我即使配置了multiple 支持多文件上传也仍旧是只支持单文件上传,经过查资料发现可能是组件内部写死了multiple: false,拒绝使用,所以现在改换使用另外的组件。

2. a-upload

使用 Ant Design Vue 原生的 a-upload 实现多图上传

              <a-upload list-type="picture-card" :multiple="true" :file-list="fileList" :before-upload="beforeUpload"
                @change="handleUploadChange" @preview="handlePreview" :action="uploadUrl" :headers="headers">
                <!-- 显示上传按钮(限制最大数量) -->
                <div v-if="fileList.length < 10">
                  <a-icon type="plus" />
                  <div class="ant-upload-text">上传图片</div>
                </div>
              </a-upload>

              <!-- 图片预览模态框 -->
              <a-modal :visible="previewVisible" :footer="null" @cancel="previewVisible = false">
                <img style="width: 100%" :src="previewImage" />
              </a-modal>

上传一张图片可以预览一张,效果如下图
在这里插入图片描述

注意:使用这个组件需要自定义请求头和上传地址

 data() {
    return {
      //多图片上传
      fileList: [],           // 存储已上传的文件
      previewVisible: false,   // 控制预览模态框
      previewImage: '',        // 当前预览的图片URL
      uploadUrl: `${window._CONFIG['domianURL']}/sys/common/upload`, // 上传接口
      headers: {
        'X-Access-Token': this.$store.getters.token, //请求头可自己更换
      },
  },

同时加上这些方法就可以使用啦

methods: {
    // 上传前的校验(文件类型、大小)
    beforeUpload(file) {
      const isImage = ['image/jpeg', 'image/png', 'image/jpg'].includes(file.type);
      const isLt2M = file.size / 1024 / 1024 < 2;

      if (!isImage) {
        this.$message.error('只能上传 JPG/PNG 格式的图片!');
      }
      if (!isLt2M) {
        this.$message.error('图片大小不能超过 2MB!');
      }
      return isImage && isLt2M;
    },

    // 处理上传状态变化
    handleUploadChange({ file, fileList }) {
      if (file.status === 'done') {
        this.$message.success(`${file.name} 上传成功!`);
        // 将上传成功的文件URL存入 model.projectsPic
        this.model.projectsPic = fileList
          .filter(f => f.status === 'done')
          .map(f => f.response?.result || f.response?.message);
      } else if (file.status === 'error') {
        this.$message.error(`${file.name} 上传失败!`);
      }
      this.fileList = fileList;
    },

    // 图片预览
    handlePreview(file) {
      this.previewImage = file.url || file.thumbUrl;
      this.previewVisible = true;
    },
},

二、给上传图片加前缀

一个项目不可能只有一个上传图片的地方,现在不同目录下的图片难以区分,

1.用户页面

在这里插入图片描述
因为这个使用的是jeecgboot的前端框架,应后端要求修改为/user,地址为src\components\jeecg\JImageUpload.vue

<a-upload
      name="file"
      listType="picture-card"
      :multiple="isMultiple"
      :action="uploadAction"
      :headers="headers"
      :data="{biz:bizPath}"  //这里控制的地址
      :fileList="fileList"
      :beforeUpload="beforeUpload"
      :disabled="disabled"
      :isMultiple="isMultiple"
  /*这个属性用于控制文件上传的业务路径*/
      bizPath:{
        type:String,
        required:false,
        default:"user"//改成为user
      },

在这里插入图片描述

2.项目页面

data() {
   return {
	  //多图片上传
      fileList: [],           // 存储已上传的文件
      previewVisible: false,   // 控制预览模态框
      previewImage: '',        // 当前预览的图片URL
      uploadUrl: `${window._CONFIG['domianURL']}/sys/common/upload`, // 上传接口
      headers: {
        'X-Access-Token': this.$store.getters.token, // Vuex 中的 Token
      },
      uploadData: {
        biz: 'project', // 业务标识
      },
  }
}
methods: {
// 上传前的校验(文件类型、大小)
    beforeUpload(file) {
      const isImage = file.type.includes('image/');

      if (!isImage) {
        this.$message.error('只能上传图片文件!');
        return false;
      }

      return true;
    },

    // 处理上传状态变化
    handleUploadChange({ file, fileList }) {
      if (file.status === 'done') {
        const response = file.response;
        if (response.success) {
          // JEECG-Boot标准响应格式处理
          const filePath = response.result || response.message;
          this.$message.success('上传成功');

          // 更新model中的图片路径(分号分隔多个文件)
          this.model.projectsPic = fileList
            .filter(f => f.status === 'done')
            .map(f => f.response?.result || f.response?.message)
            .join(';');
        } else {
          this.$message.error(response.message || '上传失败');
        }
      } else if (file.status === 'error') {
        this.$message.error('上传失败');
      }
      this.fileList = fileList;
    },

    // 初始化已上传文件列表
    initFileList(picStr) {
      if (!picStr) {
        this.fileList = [];
        return;
      }

      this.fileList = picStr.split(';').map(url => {
        // 补全URL(使用框架配置的域名)
        const fullUrl = url.startsWith('http') ? url
          : `${window._CONFIG['domianURL']}/sys/common/view/${url}`;

        return {
          uid: -Math.random(),
          name: url.split('/').pop(),
          status: 'done',
          url: fullUrl,
          response: { result: url }
        };
      });
    },

    // 图片预览
    handlePreview(file) {
      this.previewImage = file.url || file.thumbUrl;
      this.previewVisible = true;
    },
}
<a-upload list-type="picture-card" :multiple="true" :file-list="fileList" :before-upload="beforeUpload"
 @change="handleUploadChange" 
 @preview="handlePreview" 
 :action="uploadUrl" 
 :headers="headers" 
 :data="uploadData">
  <!-- 重点是加上这个:data="uploadData" -->
<!-- 显示上传按钮(限制最大数量) -->
 <div v-if="fileList.length < 10">
     <a-icon type="plus" />
 	 <div class="ant-upload-text">上传图片
	 </div>
</div>
</a-upload>

在这里插入图片描述
在这里插入图片描述

3.其他页面

第一步

//多图片上传
fileList: [],           // 存储已上传的文件
previewVisible: false,   // 控制预览模态框
uploadUrl: `${window._CONFIG['domianURL']}/sys/common/upload`, // 上传接口
headers: {
    'X-Access-Token': this.$store.getters.token, // Vuex 中的 Token
},
previewVisible: false,
currentPreviewUrl: '',
uploadData: {
    biz: 'demand', // 业务标识
},//其实就是在前面的基础上加个uploadData

第二步

<a-upload list-type="picture-card" :multiple="true" :file-list="fileList"
    :before-upload="beforeUpload" @change="handleUploadChange" @preview="handlePreview"
    :action="uploadUrl" :headers="headers" :data="uploadData">
<!-- 在这里加上 :data="uploadData"-->
    <!-- 显示上传按钮(限制最大数量) -->
    <div v-if="fileList.length < 10">
        <a-icon type="plus" />
        <div class="ant-upload-text">上传图片</div>
    </div>
</a-upload>

检查成功
在这里插入图片描述
在这里插入图片描述

4.其他页面

这样写就可以

<a-upload list-type="picture-card" :multiple="true" :file-list="fileList"
    :before-upload="beforeUpload" @change="handleUploadChange" @preview="handlePreview"
    :action="uploadUrl" :headers="headers" :data="{ biz: 'demand' }">
<!-- 在这里加上 :data="{ biz: 'demand' }"-->
    <!-- 显示上传按钮(限制最大数量) -->
    <div v-if="fileList.length < 10">
        <a-icon type="plus" />
        <div class="ant-upload-text">上传图片</div>
    </div>
</a-upload>

三、图片预览

之前没有完整路径的时候,图片预览是个很麻烦的事情,前端要加上当前路径地址拼起来,现在因为后端返回的是全路径,只要直接显示就好了

四、图片和文件混合上传

效果图
在这里插入图片描述

<a-upload-dragger :action="uploadAction" :multiple="true" :file-list="allFileList" :headers="headers"  :data="getUploadData"
  @change="handleFileUpload" @beforeUpload="beforeFileUpload" class="margin-top">
  <span v-if="allFileList.length < 10">
    <p class="ant-upload-drag-icon">
      <a-icon type="cloud-upload" />
    </p>
    <p class="ant-upload-text">把文件或图片拖到此处或点击上传</p>
    <p class="ant-upload-hint">
      支持所有文件格式(图片会自动识别,最多上传10个文件)
    </p>
  </span>
  <span v-else>
    <p class="ant-upload-text">已达到最大上传数量(10个文件)</p>
  </span>
</a-upload-dragger>

在这里插入图片描述
比较值得说的是这次请求是自己拼接的,如图所示

data(){
	// 上传相关配置
      headers: {},
      uploadAction: window._CONFIG['domianURL'] + 'sys/common/appUpload',
      uploadData: {
        fileName: '合同信息',
        biz: 'wm_ht_pic',
      },

      // 所有文件列表(包含图片和其他文件)
      allFileList: [],
      fileListAudio: [],
}
  computed: {
    // 使用计算属性确保每次获取最新的ip值
    getUploadData() {
      return {
        ...this.uploadData,
         ip: (window._CONFIG && window._CONFIG['domianURL']) || ''
      }
    }
  },
  created() {
    // 初始化上传头部信息
    const token = Vue.ls.get(ACCESS_TOKEN)
    this.headers = { 'X-Access-Token': token }
  },
	// 解析文件字符串为文件列表
    parseFileStrToFileList(fileStr, isImage) {
      if (!fileStr || typeof fileStr !== 'string' || fileStr.trim() === '') {
        return
      }

      const fileUrls = fileStr.split(',')
      const newFiles = fileUrls
        .map((url, index) => {
          if (!url || url.trim() === '') return null

          const fileName = url.split('|')[0] || url.split('/').pop() || '未知文件'
          const fileUrl = url.split('|')[1] || url

          return {
            uid: `${isImage ? 'img' : 'file'}-${index}-${Date.now()}`,
            name: fileName,
            status: 'done',
            url: fileUrl,
            isImage: isImage,  // 标记是否为图片
            response: {
              result: [url]
            }
          }
        })
        .filter(Boolean)

      // 将新文件添加到总列表
      this.allFileList = [...this.allFileList, ...newFiles]
    },

    // 上传前检查
    beforeFileUpload(file) {
      if (this.allFileList.length >= 10) {
        this.$message.warning('最多只能上传10个文件')
        return false
      }

      // 检查文件大小
      const maxSize = file.type.includes('image/') ? 10 : 20; // 图片最大10MB,其他文件20MB
      const isLtMaxSize = file.size / 1024 / 1024 < maxSize
      if (!isLtMaxSize) {
        this.$message.warning(`${file.type.includes('image/') ? '图片' : '文件'}大小不能超过${maxSize}MB`)
        return false
      }

      return true
    },

    // 处理文件上传变化
    handleFileUpload({ file, fileList }) {
      // 标记文件是否为图片
      file.isImage = this.isImageFile(file.name)

      // 确保文件列表不超过10个
      if (fileList.length > 10) {
        this.allFileList = fileList.slice(0, 10)
        this.$message.warning('最多只能上传10个文件')
      } else {
        this.allFileList = fileList.map(f => ({
          ...f,
          isImage: this.isImageFile(f.name)
        }))
      }

      // 处理上传错误
      if (file.status === 'error') {
        this.$message.error(`文件 ${file.name} 上传失败`)
      }
    },

    // 判断是否为图片文件
    isImageFile(fileName) {
      const ext = fileName.split('.').pop().toLowerCase()
      return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)
    },

    // 移除文件
    handleRemoveFile(file) {
      const index = this.allFileList.findIndex(item => item.uid === file.uid)
      if (index !== -1) {
        this.allFileList.splice(index, 1)
      }
    },

    // 根据文件名获取对应的图标
    getIconByFileName(fileName) {
      const ext = fileName.split('.').pop().toLowerCase()

      if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) {
        return 'picture'
      } else if (ext === 'pdf') {
        return 'file-pdf'
      } else if (['doc', 'docx'].includes(ext)) {
        return 'file-word'
      } else if (['xls', 'xlsx'].includes(ext)) {
        return 'file-excel'
      } else if (['ppt', 'pptx'].includes(ext)) {
        return 'file-ppt'
      } else if (ext === 'txt') {
        return 'file-text'
      } else if (['zip', 'rar', '7z'].includes(ext)) {
        return 'file-zip'
      } else {
        return 'file'
      }
    },

    // 分别转换图片和文件列表为字符串
    convertFilesToString() {
      // 分离图片和其他文件
      const imageFiles = this.allFileList.filter(file => file.isImage && file.status === 'done' && file.response)
      const otherFiles = this.allFileList.filter(file => !file.isImage && file.status === 'done' && file.response)

      // 转换为字符串格式
      const picStr = imageFiles
        .map(file => file.response.result[0] || `${file.name}|${file.url}`)
        .join(',')

      const fileStr = otherFiles
        .map(file => file.response.result[0] || `${file.name}|${file.url}`)
        .join(',')

      return { picStr, fileStr }
    },

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值