SpringBoot + Vue实现批量导入导出功能的标准方案

用固定资产管理系统中的资产模块进行示范批量导入导出功能

下面我将提供一个完整的SpringBoot + Vue实现的固定资产批量导入导出功能,这个项目大家也可以进行看我的资产管理系统

项目:https://blog.csdn.net/qq_46108094

下面开始项目实操

后端实现 (Spring Boot)

1. 实体类 (Assets.java)

java 复制代码
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

@Data
@TableName("assets")
public class Assets {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String assetCode;      // 资产编码
    private String assetName;      // 资产名称
    private String assetType;      // 资产类型
    private String specification;  // 规格型号
    private BigDecimal unitPrice;  // 单价
    private Integer quantity;      // 数量
    private BigDecimal totalValue; // 总价值
    private String department;     // 使用部门
    private String user;           // 使用人
    private String location;       // 存放地点
    private String status;         // 状态:在用、闲置、报废等
    
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

2. 控制器 (AssetsController.java)

java 复制代码
package com.example.demo.controller;

import com.example.demo.entity.Assets;
import com.example.demo.service.AssetsService;
import com.example.demo.common.Result;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;

@RestController
@RequestMapping("/assets")
public class AssetsController {

    @Resource
    private AssetsService assetsService;

    /**
     * 导出所有资产数据
     */
    @GetMapping("/export")
    public void export(HttpServletResponse response) throws Exception {
        // 查询所有数据
        List<Assets> list = assetsService.list();
        
        // 在内存操作,写出到浏览器
        ExcelWriter writer = ExcelUtil.getWriter(true);
        
        // 自定义标题别名
        writer.addHeaderAlias("assetCode", "资产编码");
        writer.addHeaderAlias("assetName", "资产名称");
        writer.addHeaderAlias("assetType", "资产类型");
        writer.addHeaderAlias("specification", "规格型号");
        writer.addHeaderAlias("unitPrice", "单价");
        writer.addHeaderAlias("quantity", "数量");
        writer.addHeaderAlias("totalValue", "总价值");
        writer.addHeaderAlias("department", "使用部门");
        writer.addHeaderAlias("user", "使用人");
        writer.addHeaderAlias("location", "存放地点");
        writer.addHeaderAlias("status", "状态");
        writer.addHeaderAlias("createTime", "创建时间");
        
        // 一次性写出list内的对象到excel,使用默认样式,强制输出标题
        writer.write(list, true);
        
        // 设置浏览器响应的格式
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("固定资产列表", "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
        
        ServletOutputStream out = response.getOutputStream();
        writer.flush(out, true);
        out.close();
        writer.close();
    }

    /**
     * 导入Excel数据
     */
    @PostMapping("/import")
    public Result importData(MultipartFile file) throws Exception {
        InputStream inputStream = file.getInputStream();
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        
        // 通过JavaBean的方式读取Excel内的对象,要求表头必须是英文,与JavaBean的属性对应
        List<Assets> list = reader.readAll(Assets.class);
        
        if (CollUtil.isEmpty(list)) {
            return Result.error("Excel文件中没有数据");
        }
        
        // 批量保存
        boolean result = assetsService.saveBatch(list);
        return result ? Result.success("导入成功") : Result.error("导入失败");
    }

    /**
     * 下载导入模板
     */
    @GetMapping("/template")
    public void downloadTemplate(HttpServletResponse response) throws Exception {
        // 创建空数据示例
        Assets template = new Assets();
        template.setAssetCode("ASSET-001");
        template.setAssetName("示例资产");
        template.setAssetType("电子设备");
        template.setSpecification("标准型号");
        template.setUnitPrice(new BigDecimal("1000.00"));
        template.setQuantity(1);
        template.setTotalValue(new BigDecimal("1000.00"));
        template.setDepartment("行政部");
        template.setUser("张三");
        template.setLocation("A区101室");
        template.setStatus("在用");
        
        List<Assets> list = CollUtil.newArrayList(template);
        
        // 在内存操作,写出到浏览器
        ExcelWriter writer = ExcelUtil.getWriter(true);
        
        // 自定义标题别名
        writer.addHeaderAlias("assetCode", "资产编码");
        writer.addHeaderAlias("assetName", "资产名称");
        writer.addHeaderAlias("assetType", "资产类型");
        writer.addHeaderAlias("specification", "规格型号");
        writer.addHeaderAlias("unitPrice", "单价");
        writer.addHeaderAlias("quantity", "数量");
        writer.addHeaderAlias("totalValue", "总价值");
        writer.addHeaderAlias("department", "使用部门");
        writer.addHeaderAlias("user", "使用人");
        writer.addHeaderAlias("location", "存放地点");
        writer.addHeaderAlias("status", "状态");
        
        // 只导出别名字段
        writer.setOnlyAlias(true);
        
        // 一次性写出list内的对象到excel,使用默认样式,强制输出标题
        writer.write(list, true);
        
        // 设置浏览器响应的格式
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("固定资产导入模板", "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
        
        ServletOutputStream out = response.getOutputStream();
        writer.flush(out, true);
        out.close();
        writer.close();
    }
}

3. 服务接口 (AssetsService.java)

java 复制代码
package com.example.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.Assets;
import java.util.List;

public interface AssetsService extends IService<Assets> {
    // 可以添加自定义方法
}

4. 服务实现 (AssetsServiceImpl.java)

java 复制代码
package com.example.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Assets;
import com.example.demo.mapper.AssetsMapper;
import com.example.demo.service.AssetsService;
import org.springframework.stereotype.Service;

@Service
public class AssetsServiceImpl extends ServiceImpl<AssetsMapper, Assets> implements AssetsService {
    // 实现自定义方法
}

5. 统一返回结果类 (Result.java)

java 复制代码
package com.example.demo.common;

import lombok.Data;
import java.io.Serializable;

@Data
public class Result implements Serializable {
    private int code;
    private String msg;
    private Object data;
    
    public static Result success() {
        Result result = new Result();
        result.setCode(200);
        result.setMsg("成功");
        return result;
    }
    
    public static Result success(Object data) {
        Result result = new Result();
        result.setCode(200);
        result.setMsg("成功");
        result.setData(data);
        return result;
    }
    
    public static Result success(String msg, Object data) {
        Result result = new Result();
        result.setCode(200);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
    
    public static Result error() {
        Result result = new Result();
        result.setCode(500);
        result.setMsg("失败");
        return result;
    }
    
    public static Result error(String msg) {
        Result result = new Result();
        result.setCode(500);
        result.setMsg(msg);
        return result;
    }
    
    public static Result error(int code, String msg) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

前端实现 (Vue + Element UI)

1. 固定资产页面 (Assets.vue)

vue 复制代码
<template>
  <div class="app-container">
    <!-- 搜索和操作区域 -->
    <div class="filter-container">
      <el-input v-model="listQuery.assetName" placeholder="资产名称" style="width: 200px;" class="filter-item" />
      <el-select v-model="listQuery.department" placeholder="使用部门" clearable style="width: 200px" class="filter-item">
        <el-option v-for="item in departmentOptions" :key="item" :label="item" :value="item" />
      </el-select>
      <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
        搜索
      </el-button>
      <el-button class="filter-item" style="margin-left: 10px;" type="primary" icon="el-icon-edit" @click="handleCreate">
        新增
      </el-button>
      <el-button type="success" plain @click="exportData">批量导出</el-button>
      <el-button type="info" plain @click="handleImport">批量导入</el-button>
    </div>

    <!-- 数据表格 -->
    <el-table
      :key="tableKey"
      v-loading="listLoading"
      :data="list"
      border
      fit
      highlight-current-row
      style="width: 100%;"
    >
      <el-table-column label="ID" prop="id" align="center" width="80">
        <template slot-scope="{row}">
          <span>{{ row.id }}</span>
        </template>
      </el-table-column>
      <el-table-column label="资产编码" width="120" align="center">
        <template slot-scope="{row}">
          <span>{{ row.assetCode }}</span>
        </template>
      </el-table-column>
      <el-table-column label="资产名称" min-width="150">
        <template slot-scope="{row}">
          <span>{{ row.assetName }}</span>
        </template>
      </el-table-column>
      <el-table-column label="资产类型" width="120" align="center">
        <template slot-scope="{row}">
          <span>{{ row.assetType }}</span>
        </template>
      </el-table-column>
      <el-table-column label="规格型号" width="120" align="center">
        <template slot-scope="{row}">
          <span>{{ row.specification }}</span>
        </template>
      </el-table-column>
      <el-table-column label="单价" width="100" align="center">
        <template slot-scope="{row}">
          <span>¥{{ row.unitPrice }}</span>
        </template>
      </el-table-column>
      <el-table-column label="数量" width="80" align="center">
        <template slot-scope="{row}">
          <span>{{ row.quantity }}</span>
        </template>
      </el-table-column>
      <el-table-column label="总价值" width="120" align="center">
        <template slot-scope="{row}">
          <span>¥{{ row.totalValue }}</span>
        </template>
      </el-table-column>
      <el-table-column label="使用部门" width="120" align="center">
        <template slot-scope="{row}">
          <span>{{ row.department }}</span>
        </template>
      </el-table-column>
      <el-table-column label="使用人" width="100" align="center">
        <template slot-scope="{row}">
          <span>{{ row.user }}</span>
        </template>
      </el-table-column>
      <el-table-column label="存放地点" width="120" align="center">
        <template slot-scope="{row}">
          <span>{{ row.location }}</span>
        </template>
      </el-table-column>
      <el-table-column label="状态" width="100" align="center">
        <template slot-scope="{row}">
          <el-tag :type="row.status | statusFilter">{{ row.status }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" width="230" class-name="small-padding fixed-width">
        <template slot-scope="{row,$index}">
          <el-button type="primary" size="mini" @click="handleUpdate(row)">
            编辑
          </el-button>
          <el-button v-if="row.status!='deleted'" size="mini" type="danger" @click="handleDelete(row,$index)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />

    <!-- 批量导入对话框 -->
    <el-dialog title="批量导入资产" :visible.sync="importVisible" width="40%" :close-on-click-modal="false" destroy-on-close>
      <div style="margin-bottom: 20px;">
        <el-alert
          title="导入说明"
          type="info"
          :closable="false"
          show-icon>
          <div slot="default">
            <p>1. 请先<a href="javascript:void(0);" @click="downloadTemplate" style="color: #409EFF; text-decoration: underline;">下载模板</a>,按照模板格式填写数据</p>
            <p>2. 仅支持xlsx格式文件,且文件大小不超过10MB</p>
            <p>3. 每次最多导入1000条数据</p>
          </div>
        </el-alert>
      </div>

      <el-upload
        class="upload-demo"
        drag
        action="#"
        :auto-upload="false"
        :on-change="handleUploadChange"
        :before-upload="beforeUpload"
        :file-list="fileList"
        :limit="1"
        accept=".xlsx,.xls"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <div class="el-upload__tip" slot="tip">只能上传xlsx/xls文件,且不超过10MB</div>
      </el-upload>

      <div slot="footer" class="dialog-footer">
        <el-button @click="importVisible = false">取 消</el-button>
        <el-button type="primary" :loading="importLoading" @click="submitImport">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { fetchList, importAssets } from '@/api/assets'
import Pagination from '@/components/Pagination'

export default {
  name: 'AssetsList',
  components: { Pagination },
  filters: {
    statusFilter(status) {
      const statusMap = {
        '在用': 'success',
        '闲置': 'info',
        '报废': 'danger'
      }
      return statusMap[status]
    }
  },
  data() {
    return {
      tableKey: 0,
      list: null,
      total: 0,
      listLoading: true,
      listQuery: {
        page: 1,
        limit: 20,
        assetName: undefined,
        department: undefined
      },
      departmentOptions: ['行政部', '财务部', '技术部', '市场部', '人事部'],
      importVisible: false,
      importLoading: false,
      fileList: [],
      uploadFile: null
    }
  },
  created() {
    this.getList()
  },
  methods: {
    getList() {
      this.listLoading = true
      fetchList(this.listQuery).then(response => {
        this.list = response.data.items
        this.total = response.data.total
        this.listLoading = false
      })
    },
    handleFilter() {
      this.listQuery.page = 1
      this.getList()
    },
    handleCreate() {
      // 新增资产逻辑
    },
    handleUpdate(row) {
      // 编辑资产逻辑
    },
    handleDelete(row, index) {
      // 删除资产逻辑
    },
    // 导出数据
    exportData() {
      window.open(`${process.env.VUE_APP_BASE_API}/assets/export`)
    },
    // 打开导入对话框
    handleImport() {
      this.importVisible = true
      this.fileList = []
    },
    // 下载模板
    downloadTemplate() {
      window.open(`${process.env.VUE_APP_BASE_API}/assets/template`)
    },
    // 上传文件前的校验
    beforeUpload(file) {
      const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || 
                      file.type === 'application/vnd.ms-excel'
      const isLt10M = file.size / 1024 / 1024 < 10

      if (!isExcel) {
        this.$message.error('只能上传Excel文件!')
      }
      if (!isLt10M) {
        this.$message.error('上传文件大小不能超过10MB!')
      }
      return isExcel && isLt10M
    },
    // 文件改变时的回调
    handleUploadChange(file, fileList) {
      const isExcel = file.raw.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || 
                      file.raw.type === 'application/vnd.ms-excel'
      if (!isExcel) {
        this.$message.error('只能上传Excel文件!')
        this.fileList = []
        return false
      }
      this.uploadFile = file.raw
    },
    // 提交导入
    submitImport() {
      if (!this.uploadFile) {
        this.$message.warning('请先选择文件')
        return
      }
      
      this.importLoading = true
      const formData = new FormData()
      formData.append('file', this.uploadFile)
      
      importAssets(formData).then(response => {
        this.importLoading = false
        this.$message({
          message: response.msg || '导入成功',
          type: 'success'
        })
        this.importVisible = false
        this.getList() // 刷新列表
      }).catch(() => {
        this.importLoading = false
      })
    }
  }
}
</script>

<style scoped>
.filter-container {
  margin-bottom: 20px;
}
.upload-demo {
  text-align: center;
}
</style>

2. API接口 (assets.js)

javascript 复制代码
import request from '@/utils/request'

export function fetchList(query) {
  return request({
    url: '/assets/list',
    method: 'get',
    params: query
  })
}

export function importAssets(data) {
  return request({
    url: '/assets/import',
    method: 'post',
    data,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
}

使用说明

1. 后端依赖

在pom.xml中添加以下依赖:

xml 复制代码
<!-- Hutool工具包 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.17</version>
</dependency>

<!-- MyBatis Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.4</version>
</dependency>

<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2. 功能特点

  1. 批量导出:将数据库中的固定资产数据导出为Excel文件
  2. 批量导入:通过Excel模板批量导入固定资产数据
  3. 模板下载:提供标准导入模板,确保数据格式一致性
  4. 数据校验:前端和后端都对上传文件进行校验
  5. 用户体验:提供清晰的操作指引和反馈

3. 使用流程

  1. 点击"批量导入"按钮打开导入对话框
  2. 下载导入模板并按照格式填写数据
  3. 将填写好的Excel文件拖拽或点击上传
  4. 点击确定开始导入,系统会显示导入结果
  5. 导入成功后,页面数据自动刷新

这个实现方案提供了完整的固定资产批量导入导出功能 ,包含了前后端代码

大家可以通过这个进行修改然后直接集成到自己的SpringBoot + Vue项目中

相关推荐
2401_8370885033 分钟前
ref 简单讲解
前端·javascript·vue.js
David爱编程44 分钟前
Java 守护线程 vs 用户线程:一文彻底讲透区别与应用
java·后端
小奏技术1 小时前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术1 小时前
Spring AI存储向量数据
后端
苏三的开发日记1 小时前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
苏三的开发日记1 小时前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
陈三一1 小时前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure1 小时前
万字详解JVM
java·jvm·后端
我崽不熬夜2 小时前
Java的条件语句与循环语句:如何高效编写你的程序逻辑?
java·后端·java ee
前端小巷子2 小时前
Vue3的渲染秘密:从同步批处理到异步微任务
前端·vue.js·面试