先看一下效果图

一、客户端

<!--test_table-->
<template>
  <div>
    <el-card>
      <el-upload
        ref="upload"
        action="/api/upload/addPicture"
        :file-list="fileList"
        :auto-upload="true"
        :on-success="handleAvatarSuccess"
        :show-file-list="false"
        multiple
      >
        <el-button slot="trigger" type="primary">上传文件</el-button>
      </el-upload>
      <br />
      <el-button @click="handleBatchDownload()">批量下载</el-button>
      <el-input
        placeholder="输入文件名搜索"
        v-model="input_searchFileName"
        class="input-with-select"
        style="width: 400px; float: right"
      >
        <el-button slot="append" icon="el-icon-search"></el-button>
      </el-input>

      <div style="margin: 20px 0">
        <el-breadcrumb separator="/">
          <el-breadcrumb-item :to="{ path: '/' }">全部文件</el-breadcrumb-item>
          <el-breadcrumb-item>目录1</el-breadcrumb-item>
        </el-breadcrumb>
      </div>

      <el-table
        ref="multipleTable"
        :data="tableData"
        tooltip-effect="dark"
        style="width: 100%"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="55"> </el-table-column>

        <el-table-column prop="filename" label="文件名" width="220">
        </el-table-column>
        <el-table-column prop="size" label="大小" width="220">
        </el-table-column>
        <el-table-column prop="mimetype" label="类型" width="220">
        </el-table-column>
        <el-table-column prop="updatetime" label="上传日期" width="220">
        </el-table-column>
        <el-table-column
          prop="filepath"
          label="文件路径"
          width="220"
          v-if="false"
        >
        </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <el-button
              size="mini"
              @click="handlePreview(scope.$index, scope.row)"
              >预览</el-button
            >
            <el-button
              size="mini"
              type="danger"
              @click="handleDelete(scope.$index, scope.row)"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script>
import axios from "axios";
import JSZip from "jszip";
import FileSaver from "file-saver";
export default {
  name: "",
  components: {},
  data() {
    return {
      tableData: [],
      multipleSelection: [],
      fileList: [],
      input_searchFileName: "",
    };
  },
  props: {},
  created() {},
  mounted() {},
  computed: {},
  methods: {
    toggleSelection(rows) {
      if (rows) {
        rows.forEach((row) => {
          this.$refs.multipleTable.toggleRowSelection(row);
        });
      } else {
        this.$refs.multipleTable.clearSelection();
      }
    },
    handleSelectionChange(val) {
      this.multipleSelection = val;
      console.log("this.multipleSelection", this.multipleSelection);
    },
    handlePreview(index, row) {
      window.open("/api" + row.filepath);
    },
    handleDelete(index, row) {
      console.log(index, row);
    },
    handleAvatarSuccess(res, file) {
      let { filename, size, mimetype, updatetime, filepath } = res.info;
      console.log("filepath", filepath);
      size = (size / 1024).toFixed(2) + " KB";
      this.tableData.unshift({
        filename,
        size,
        mimetype,
        updatetime,
        filepath,
      });
    },

    getFile(url) {
      return new Promise((resolve, reject) => {
        axios({
          method: "get",
          url,
          responseType: "arraybuffer", //必须加这个!可处理txt、png、mp4、tif等格式,不能处理docx、pptx,xlsx
        })
          .then((res) => {
            resolve(res.data);
          })
          .catch((error) => {
            reject(error.toString());
          });
      });
    },
    handleBatchDownload() {
      if (this.multipleSelection.length == 0) {
        this.$message.error("请选择下载文件");
        return;
      }
      let filePathSelected = this.multipleSelection.map((item) => {
        return "/api" + item.filepath;
      });
      const zip = new JSZip();
      const cache = {};
      const promises = [];
      filePathSelected.forEach((item) => {
        const promise = this.getFile(item).then((data) => {
          // 下载文件, 并存成ArrayBuffer对象
          const arr_name = item.split("/");
          const file_name = arr_name[arr_name.length - 1]; // 获取文件名
          zip.file(file_name, data, { binary: true }); // 逐个添加文件
          cache[file_name] = data;
        });
        promises.push(promise);
      });

      // Promise.all可以将多个Promise实例包装成一个新的Promise实例。
      // 同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,
      // 而失败的时候则返回最先被reject失败状态的值。
      Promise.all(promises).then(() => {
        zip.generateAsync({ type: "blob" }).then((content) => {
          // 生成二进制流
          FileSaver.saveAs(content, "打包下载.zip"); // 利用file-saver保存文件
          this.$message.success("下载成功");
        });
      });
    },
  },
  watch: {},
};
</script>

<style lang="scss" scoped>
</style>

处理跨域,项目根目录下新建vue.config.js

module.exports = {
    lintOnSave: false, //取消eslint检测
        devServer: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000', //设置开发环境服务域名和端口号 别忘了加http
                changeOrigin: true, //這裡true表示实现跨域
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
}

二、服务端

连接数据库。项目根目录下新建db.js

即:server/db.js

var pgsql = require("pg");
var pool = new pgsql.Pool({
    host: 'localhost', //连接本地数据库
    user: 'postgres',
    password: 'user',
    database: 'test_file_manage'
});

function query(sql, callback) {
    pool.connect(function (err, connection) {
        connection.query(sql, function (err, result) {
            callback(err, result);
            connection.release();
        });
    });
}

exports.query = query;

server/routes/upload.js

// routes/upload.js
var express = require('express');
var router = express.Router();
const multer = require('multer');
var dayjs=require('dayjs')
var db = require('../db.js')

const storage = multer.diskStorage({
    //存储的位置
    destination: 'public/uploadFiles/', // 保存的路径,备注:需要自己创建
    //文件名字的确定 multer默认帮我们取一个没有扩展名的文件名,因此需要我们自己定义
    filename(req, file, cb) {
        // cb(null, Date.now() + file.originalname)
        cb(null, file.originalname)
 
    }
})
const upload = multer({
    storage
})
router.post('/addPicture', upload.single('file'), (req, res) => {
    //线上的也就是服务器中的图片的绝对地址
    let { mimetype, filename, size } = req.file;
    let filepath = '/uploadFiles/' + filename
    let updatetime=dayjs(new Date()).format("YYYY-MM-DD")
    let sql_add = `insert into files(filename,mimetype,size,filepath,updatetime) values ('${filename}','${mimetype}',${size},'${filepath}','${updatetime}')`;
    db.query(sql_add, function (err, rows) {
      if (err) {
        console.log('err:', err.message);
      } else {
          console.log("rows:", rows);
         
        const data = {
          code: 1,
            msg: "success",
            info: {
                mimetype, filename, size,filepath,updatetime
          }
        }
        res.send(data)
      }
    });
})



module.exports = router;

 app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var uploadRouter = require('./routes/upload')
var cors=require("cors")

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(cors())
app.use('/', indexRouter);
app.use('/users', usersRouter);

app.use('/upload', uploadRouter);



// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

const port=3000;
app.listen(port,function(){
	console.log(`服务器运行在 http://localhost:${port}`)
})

module.exports = app;

三、数据库

更多推荐