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项目中

相关推荐
程序员NEO几秒前
B站油管抖音一键笔记
后端
C++chaofan2 分钟前
游标查询在对话历史场景下的独特优势
java·前端·javascript·数据库·spring boot
cg.family4 分钟前
Vue3 v-slot 详解与示例
前端·javascript·vue.js
golang学习记5 分钟前
Python 2025 最火框架排名盘点:FastAPI 成为新王
后端
程序新视界8 分钟前
MySQL的两种分页方式:Offset/Limit分页和游标分页
后端·sql·mysql
小蒜学长9 分钟前
springboot房地产销售管理系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
齐 飞19 分钟前
Spring Cloud Alibaba快速入门-Sentinel熔断规则
spring boot·spring cloud·sentinel
邂逅星河浪漫31 分钟前
【LangChain4j+Redis】会话记忆功能实现
java·spring boot·后端·阿里云·langchain4j·会话记忆
API开发34 分钟前
apiSQL+GoView:一个API接口开发数据大屏
前端·后端·api·数据可视化·数据大屏·apisql
aloha_78940 分钟前
新国都面试真题
jvm·spring boot·spring·面试·职场和发展