vue上传各种文件,并预览组件,(预览,下载),下载resources目录下文件

发布于:2025-07-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

前端组件vue

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

<template>
    <div >
        <div class="file-list" v-if="existingFiles.length > 0">
        <div class="file-card" v-for="(file, index) in existingFiles" :key="file.id">
            <div class="file-preview">
                <i v-if="file.type === 'pdf'" class="el-icon-document pdf-preview"></i>
                <i v-else-if="file.type === 'doc' || file.type === 'docx'" class="el-icon-document doc-preview"></i>
                <i v-else-if="file.type === 'image'" class="el-icon-picture-outline"></i>
                <i v-else class="el-icon-document other-preview"></i>
            </div>
            <div class="file-name">{{ file.originalFileName }}</div>
            <div class="file-actions">
                <el-button type="primary" icon="el-icon-view" size="mini" circle @click="handlePreview(file)"></el-button>
                <el-button type="success" icon="el-icon-download" size="mini" v-if="showDownLoadBut" circle @click="downloadFile(file)"></el-button>
                <el-button type="danger" icon="el-icon-delete" size="mini" v-if="showDeleteBut" circle @click="handleDelete(file, index)"></el-button>
            </div>
        </div>
        </div>

        <el-alert
        v-else
        title="当前尚未上传任何合同"
        type="warning"
        show-icon
        style="margin: 20px 0;">
        </el-alert>

        <div class="upload-demo" v-if="existsUploadBut">
            <el-upload
                ref="upload"
                :action="uploadUrl"
                :headers="headers"
                multiple
                :on-success="handleUploadSuccess"
                :show-file-list="false"
                accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
            >
                <el-button size="medium" type="primary" icon="el-icon-plus">添加合同文件</el-button>
                <div slot="tip" class="el-upload__tip" style="margin-top: 10px;">
                支持上传PDF、Word文档和图片文件,单个文件不超过10MB
                </div>
            </el-upload>
        </div>
    
    <!-- 文件预览弹窗 -->
    <el-dialog
      :title="previewTitle"
      :visible.sync="previewVisible"
      width="80%"
      top="5vh"
    >
      <div class="preview-container">
        <img v-if="previewType === 'image'" :src="previewUrl">
        <iframe
          v-else-if="previewType === 'pdf'"
          :src="previewUrl"
          class="preview-iframe"
        ></iframe>
        <div v-else>
          <p>不支持在线预览,请下载后查看</p>
          <el-button type="primary" icon="el-icon-download" @click="downloadFile(previewFile)">
            下载文件
          </el-button>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
<script>

export default {
  name: "FileUploadPreview",
  props:{
    // 上传文件目录
     fileDirectory: {
        type: String,
        require: true,
        default: ''
    },
    // 文件列表
    fileList:{
        type:Array,
        default:()=>[]
    },
    // 上传按钮
    existsUploadBut:{
        type: Boolean,
        default: false
    },
    // 下载按钮是否显示
    showDownLoadBut:{
        type: Boolean,
        default: false
    },
    // 删除按钮
    showDeleteBut:{
        type: Boolean,
        default: false
    }
  },
  data() {
    return {
      previewVisible: false,
      existingFiles: this.fileList,
      previewUrl: '',
      previewType: '',
      previewTitle: '查看附件',
      previewFile: null,
      // 请求头 - 包含认证Token
      headers: {
        Authorization: 'Bearer ' + this.$store.getters.token
      },
      uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload?fileDirectory="+this.fileDirectory,
      imageDialogVisible: false, // 图片弹窗可见性
      previewVisible: false, // 预览弹窗可见性
      previewImageUrl: '', // 预览图片URL
      tempImageList: [], // 临时图片列表
    };
  },
  created() {

  },
  methods: {

    // 移除待上传文件
    removeNewFile(index) {
      this.existingFiles.splice(index, 1);
    },

    // 预览文件
    handlePreview(file) {
      this.previewFile = file;
      this.previewTitle = `预览文件 - ${file.originalFileName}`;
      this.previewUrl = file.url==null?file.filePath:"";
      this.previewType = this.getFileType(file.originalFileName);
      this.previewVisible = true;
    },

    // 删除文件
    handleDelete(file, index) {
      this.$confirm(`确定要删除合同 "${file.originalFileName}"?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 模拟API删除请求
        setTimeout(() => {
          this.existingFiles.splice(index, 1);
          this.$message.success(`合同 "${file.originalFileName}" 已删除`);
        }, 500);
      }).catch(() => {});
    },

    // 获取文件类型
    getFileType(filename) {
      const ext = filename.split('.').pop().toLowerCase();
      if (['jpg','jpeg','png','gif'].includes(ext)) return 'image';
      if (ext === 'pdf') return 'pdf';
      if (['doc','docx'].includes(ext)) return 'doc';
      return 'other';
    },

    // 格式化文件大小
    formatFileSize(bytes) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    },

    // 下载文件
    downloadFile(file) {
      this.$message.info(`开始下载: ${file.originalFileName}`);
      // 实际项目中这里应该是文件下载逻辑
      this.$download.systemDownLoadFile(file.filePath,file.originalFileName);
    },

    // 打开图片弹窗
    openImageDialog(row) {
      this.imageDialogVisible = true;

      // 加载已有图片 (实际项目中从后端获取)
      getWarehouseFiles(row.uuid).then(resp=>{
        this.tempImageList = resp.data;
      });
    },

    // 图片上传前校验
    beforeImageUpload(file) {
      const isImage = file.type.startsWith('image/');
      const isPdf = file.type === 'application/pdf';
      const isLt10M = file.size / 1024 / 1024 < 10;

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

      if (!isLt10M) {
        this.$message.error('文件大小不能超过10MB!');
        return false;
      }

      return true;
    },

    // 图片上传成功处理
    handleUploadSuccess(response, file, fileList) {
      // 实际项目中根据接口返回调整
    //   this.newFiles = fileList.map(f => ({
    //     originalFileName: f.response?.originalFilename || f.originalFileName,
    //     newFileName: f.response?.newFileName || f.newFileName,
    //     filePath: f.response?.filePath || f.filePath,
    //     fileSize: f.response?.size || f.fileSize,
    //     fileShowSize: f.response?.showSize || f.fileShowSize,
    //     fileType: f.response?.fileType || f.fileType,
    //     warehouseManagerUuid: f.response?.warehouseManagerUuid || f.warehouseManagerUuid,
    //     url: f.response?.url || f.url,
    //   }));
    debugger
      if(response.code==200){
        var tempData={
          originalFileName: response.originalFilename,
          newFileName:response.newFileName,
          filePath:response.filePath,
          fileSize:response.size,
          fileShowSize: response.showSize,
          fileType:response.fileType,
          name:response.originalFilename,
          url:response.url
        }
        this.existingFiles.push(tempData);
      }
    },

    // 预览图片
    handlePictureCardPreview(file) {
      this.previewImageUrl = file.url;
      this.previewVisible = true;
    },

    // 移除图片
    handleRemove(file, fileList) {
      this.tempImageList = fileList;
    },
  }
};
</script>
<style scoped>
  body {
    font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
    background-color: #f5f7fa;
    color: #333;
    padding: 20px;
  }
  .container {
    max-width: 1200px;
    margin: 0 auto;
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    padding: 20px;
  }
  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 0;
    border-bottom: 1px solid #ebeef5;
    margin-bottom: 20px;
  }
  .header h1 {
    color: #409EFF;
    font-size: 24px;
  }
  .sub-title {
    color: #909399;
    margin: 10px 0 20px;
  }
  .contract-actions {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
  }
  .file-card {
    position: relative;
    border-radius: 6px;
    overflow: hidden;
    transition: all 0.3s;
    border: 1px solid #ebeef5;
  }
  .file-card:hover {
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  }
  .file-name {
    padding: 8px;
    font-size: 12px;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: #f8f8f8;
  }
  .file-actions {
    position: absolute;
    top: 5px;
    right: 5px;
    display: flex;
    gap: 5px;
    opacity: 0;
    transition: opacity 0.3s;
  }
  .file-card:hover .file-actions {
    opacity: 1;
  }
  .file-preview {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100px;
    background: #f5f7fa;
  }
  .file-preview i {
    font-size: 40px;
    color: #409EFF;
  }
  .pdf-preview {
    color: #F56C6C;
  }
  .doc-preview {
    color: #409EFF;
  }
  .other-preview {
    color: #909399;
  }
  .file-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 15px;
    margin-top: 20px;
  }
  .preview-container {
    width: 100%;
    height: 70vh;
  }
  .preview-container img {
    max-width: 100%;
    max-height: 100%;
    display: block;
    margin: 0 auto;
  }
  .preview-iframe {
    width: 100%;
    height: 100%;
    border: none;
  }
  .footer {
    margin-top: 20px;
    text-align: center;
    color: #909399;
    font-size: 14px;
    padding: 10px;
    border-top: 1px solid #ebeef5;
  }
  .upload-demo {
    margin-top: 20px;
  }
  .demo-table {
    margin-top: 20px;
  }
  .status-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 12px;
  }
  .status-success {
    background: #f0f9eb;
    color: #67c23a;
  }
  .status-warning {
    background: #fdf6ec;
    color: #e6a23c;
  }
</style>

使用浏览器确认保存路径

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

systemDownLoadFile(filePath,fileName) {
    var url = baseURL + "/common/systemDownLoadFile?filePath=" + encodeURIComponent(filePath)+"&fileName="+encodeURIComponent(fileName);
    axios({
      method: 'get',
      url: url,
      responseType: 'blob',
      headers: { 'Authorization': 'Bearer ' + getToken() }
    }).then((res) => {
      const isBlob = blobValidate(res.data);
      if (isBlob) {
        const blob = new Blob([res.data])
        this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
      } else {
        this.printErrMsg(res.data);
      }
    })
  },

后端代码

 /**
     * 下载文件
     *
     * @param filePath 文件路径
     * @param fileName 文件原始名
     * @param response
     * @param request
     */
    @GetMapping("/systemDownLoadFile")
    public void systemDownLoadFile(@RequestParam("filePath") String filePath, @RequestParam("fileName") String fileName, HttpServletResponse response,
                                   HttpServletRequest request) {
        try {
            if (!FileUtils.checkAllowDownload(fileName)) {
                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
            }
            String configByKey = iSysConfigService.selectConfigByKey(SysConfigConstants.UPLOAD_ROOT);
//            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
            String fileAllPath = configByKey + filePath;

            // 上传文件路径
            if (StringUtils.isEmpty(configByKey)) {
                log.info("服务器文件上传地址不能为空,请检查参数设置的  {} 是否配置值", SysConfigConstants.UPLOAD_ROOT);
                return;
            }
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            FileUtils.setAttachmentResponseHeader(response, fileName);
            FileUtils.writeBytes(fileAllPath, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载文件失败", e);
        }
    }

下载resources目录下的文件

在这里插入图片描述


    @GetMapping("/downloadTemplates")
    ResponseEntity<Resource> downloadFile(@RequestParam("fileName") String fileName) {
        // 验证文件名安全(防止路径遍历攻击)
        if (!isValidFileName(fileName)) {
            throw new FileDownloadException("文件名不合法", HttpStatus.BAD_REQUEST);
        }
        // 从resources目录加载文件
        Resource resource = new ClassPathResource("templates/" + fileName);
        // 检查文件是否存在
        if (!resource.exists()) {
            throw new FileDownloadException("文件不存在", HttpStatus.NOT_FOUND);
        }
        try {
            // 设置响应头
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + encodeFilename(fileName) + "\"");

            return ResponseEntity.ok()
                    .headers(headers)
                    .contentLength(resource.contentLength())
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(resource);
        } catch (IOException e) {
            return ResponseEntity.internalServerError().build();
        }
    }

    /**
     * 安全验证文件名(防止路径遍历攻击)
     *
     * @param fileName
     * @return
     */
    private boolean isValidFileName(String fileName) {
        return fileName != null &&
                !fileName.contains("..") &&
                !fileName.contains("/") &&
                !fileName.contains("\\");
    }

    /**
     * 处理中文文件名乱码问题
     *
     * @param fileName
     * @return
     */
    private String encodeFilename(String fileName) {
        return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }