若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出相关的知识,希望对你有一定的参考价值。


场景

使用若依前后端分离版实现Excel的导入和导出。

前端:Vue+ElementUI

后端:SpringBoot+POI+mysql

注:

关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

Excel导入

点击导入按钮时的效果

 

若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出_若依

选中Excel后

 

若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出_上传_02

首先是前端页面,添加导入的dialog

<el-dialog :title="upload.title" :visible.sync="upload.open"  append-to-body>
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
:data="updateSupport:upload.updateSupport"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的下井次数设置数据
<el-link
type="info"
style="font-size:12px"
@click="downloadTemplate(xjszTemplate.xlsx)"
>下载模板</el-link>
</div>
<div
class="el-upload__tip"
style="color:red"
slot="tip"
>提示:仅允许导入“xls”或“xlsx”格式文件!是否全勤中:1代表全勤,0代表固定次数,不得有空值!!</div>
</el-upload>

<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</el-dialog>

通过:visible.sync="upload.open"控制默认隐藏,其中upload是声明的用于存储上传相关的参数的model

需要声明它

export default 
name: "Xjcssz",
data()
return
// 导入参数
upload:
// 是否显示弹出层
open: false,
// 弹出层标题
title: "",
// 是否禁用上传
isUploading: false,
// 是否更新已经存在的数据
updateSupport: 0,
// 设置上传的请求头部
headers: Authorization: "Bearer " + getToken() ,
// 上传的地址
url: process.env.VUE_APP_BASE_API + "/kqgl/xjcssz/importData",
,

这里的getToken()是从auth中引入

import  getToken  from "@/utils/auth";

是要获取登录的token

export function getToken() 
return Cookies.get(TokenKey)

文件上传组件使用的是e-upload组件,设置其一些属性

limit限制只能选择一个文件

accept限制能选择的文件类型

headers设置请求头携带token

action设置上传请求的url

disabled设置正在上传时禁用

on-progress设置正在上传时的处理事件

on-success设置上传成功后的事件

auto-upload设置自动提交为false,用来实现手动提交时才提交

data设置上传时携带的数据

drag表示支持可拖拽

设置on-progress正在上传时将其禁用

// 文件上传中处理
handleFileUploadProgress(event, file, fileList)
this.upload.isUploading = true;
,

设置on-success上传成功后关闭上传窗口并设置上传可用,然后清除选择的文件并提示导入结果然后刷新数据。

// 文件上传成功处理
handleFileSuccess(response, file, fileList)
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$alert(response.msg, "导入结果", dangerouslyUsehtmlString: true );
this.getList();
,

这里是携带了参数 是否更新已经存在的数据,将其与勾选框进行双向数据绑定

<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的下井次数设置数据

并且作为参数在提交时进行传递

:data="updateSupport:upload.updateSupport"

注意传递参数时的格式。

然后点击确定按钮时触发事件

<el-button type="primary" @click="submitFileForm">确 定</el-button>

在事件处理中,通过设置的ref属性将表单提交

submitFileForm() 
this.$refs.upload.submit();
,

此时表单就会提交到指定的url的后台接口。

来到后台接口

@RequestMapping("/importData")
@ResponseBody
@ApiOperation("导入下井次数设置数据")
public AjaxResult importData(@RequestParam MultipartFile file, @RequestParam boolean updateSupport) throws Exception
ExcelUtil<KqXjcssz> util = new ExcelUtil<KqXjcssz>(KqXjcssz.class);
List<KqXjcssz> xjcsszList = util.importExcel(file.getInputStream());
//循环插入数据
for (KqXjcssz xjcssz:xjcsszList)
if(xjcssz.getGh()==null)

return AjaxResult.error("存在为空的工号数据");


xjcssz.setSzrq(new Date());
xjcssz.setSzr(SecurityUtils.getUsername());

//根据工号查询是否已经存在
Integer count = kqXjcsszService.isExistByGh(xjcssz.getGh());

if(count>0)

//如果设置了更新
if(updateSupport)

kqXjcsszService.updateKqXjcssz(xjcssz);
else

//选择了不更新 啥也不干


else

//之前不存在直接插入
kqXjcsszService.insertKqXjcssz(xjcssz);


return AjaxResult.success("导入成功");

这里的后台接口使用@RequestMapping接收,并且使用@ResponseBody注解响应json数据。

接受请求参数时,文件必须是@RequestParam MultipartFile file,且名称为file,如果不进行更改指定的话。

然后第二个参数要与传递时的参数名一致。

然后调用若依自带的工具类

ExcelUtil<KqXjcssz> util = new ExcelUtil<KqXjcssz>(KqXjcssz.class);
List<KqXjcssz> xjcsszList = util.importExcel(file.getInputStream());

以及实体类上的注解

/** 工号 */
@Excel(name = "工号")
private String gh;

等就能实现解析Excel的数据并获取成对象的list。

这里的导入时的模板建议用下面的导出的EXCEL作为导入模板用。

然后上传时点击下载模板时调用公共下载接口。

Excel导出

页面上添加导出按钮

<el-button
type="warning"
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="[kqgl:bcgl:export]"
>导出</el-button>

导出按钮对应的处理方法

/** 导出按钮操作 */
handleExport()
const queryParams = this.queryParams;
this.$confirm("是否确认导出所有数据项?", "警告",
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
)
.then(function ()
return exportBcgl(queryParams);
)
.then((response) =>
this.download(response.msg);
)
.catch(function () );
,

会弹窗提示,点击确定后执行exportBcgl方法,此方法是从外部js中引入

import 
exportBcgl,
from "@/api/kqgl/bcgl";

在js方法中

export function exportBcgl(query) 
return request(
url: /kqgl/bcgl/export,
method: get,
params: query
)

在此方法中发送get请求给SpringBoot后台接口。

其中request是来自request.js,封装的axios发送请求的对象。

在对应的SpringBoot后台接口

@GetMapping("/export")
public AjaxResult export(KqBcgl kqBcgl)

List<KqBcgl> list = kqBcglService.getBcListByNameToExport(kqBcgl);
ExcelUtil<KqBcgl> util = new ExcelUtil<KqBcgl>(KqBcgl.class);
return util.exportExcel(list, "bcgl");

直接调用若依自带的Excel工具类就可以实现导出。

其中KqBcgl是对应的业务的实体类,可以使用代码生成工具去生成。

在实体类中通过添加注解的方式就能实现将此属性导出,如果不加此注解则不导出

/** 编号 */
@Excel(name = "编号")
private String bcbh;

而且注解里面的name属性就是导出时那列的标题。

关于这个注解还有好多个属性,具体可以参考其源码

public @interface Excel

/**
* 导出到Excel中的名字.
*/
public String name() default "";

/**
* 日期格式, 如: yyyy-MM-dd
*/
public String dateFormat() default "";

/**
* 读取内容转表达式(如:0=男,1=女,2=未知)
*/
public String readConverterExp() default "";

/**
* 导出类型(0数字 1字符串)
*/
public ColumnType cellType() default ColumnType.STRING;

/**
* 导出时在excel中每个列的高度 单位为字符
*/
public double height() default 14;

/**
* 导出时在excel中每个列的宽 单位为字符
*/
public double width() default 16;

/**
* 文字后缀,如% 90 变成90%
*/
public String suffix() default "";

/**
* 当值为空时,字段的默认值
*/
public String defaultValue() default "";

/**
* 提示信息
*/
public String prompt() default "";

/**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default ;

/**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/
public boolean isExport() default true;

/**
* 另一个类中的属性名称,支持多级获取,以小数点隔开
*/
public String targetAttr() default "";

/**
* 字段类型(0:导出导入;1:仅导出;2:仅导入)
*/
Type type() default Type.ALL;

public enum Type

ALL(0), EXPORT(1), IMPORT(2);
private final int value;

Type(int value)

this.value = value;


public int value()

return this.value;



public enum ColumnType

NUMERIC(0), STRING(1);
private final int value;

ColumnType(int value)

this.value = value;


public int value()

return this.value;


还有一种情况是,在导出前的查询数据的方法,如果调用的是和查询接口一样的方法。

某些属性比如某某状态等需要用到字典表的列。在查询接口可能就是直接查询出来,返回值

直接就是1或者2等这些字典的值。然后返回给前端,前端再进行格式化显示。

但是在导出时必须要显示对应的字典表的label,所以需要修改查询数据的方法getBcListByNameToExport

将要查询的表与字典表相关联,查询出其label值作为对应的属性,如果有多个需要关联字典表的属性,则关联两次,下面是示例代码

<select id="getBcListByNameToExport" parameterType="KqBcgl" resultMap="KqBcglResult">
SELECT
b.id,
b.bcbh,
b.bcmc,
s.dict_label AS bclx,
sfkt,
b.xss,
b.jgs,
b.sfyb,
kqts,
b.mzxx,
b.bz,
s1.dict_label AS jxbclx
FROM
kq_bcgl b
LEFT JOIN sys_dict_data s ON b.bclx = s.dict_value
AND s.dict_type = "kq_kqgl_bcgl_bclx"
LEFT JOIN sys_dict_data s1 ON b.jxbclx = s1.dict_value
AND s1.dict_type = "kq_kqgl_bcgl_jxbclx"
<where>
<if test="bcmc != null and bcmc != "> and bcmc LIKE "%"#bcmc"%"</if>
</where>
</select>

那么点击导出按钮就能实现导出了

 

若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出_css_03

RuoYi(若依开源框架)-前后端分离版-前端流程简单分析

RuoYi(若依开源框架)-前后端分离版-前端流程简单分析

项目结构

├── build // 构建相关
├── bin // 执行脚本
├── public // 公共文件
│ ├── favicon.ico // favicon图标
│ └── index.html // html模板
├── src // 源代码
│ ├── api // 所有请求
│ ├── assets // 主题 字体等静态资源
│ ├── components // 全局公用组件
│ ├── directive // 全局指令
│ ├── layout // 布局
│ ├── router // 路由
│ ├── store // 全局 store管理
│ ├── utils // 全局公用方法
│ ├── views // view
│ ├── App.vue // 入口页面
│ ├── main.js // 入口 加载组件 初始化等
│ ├── permission.js // 权限管理
│ └── settings.js // 系统配置
├── .editorconfig // 编码格式
├── .env.development // 开发环境配置
├── .env.production // 生产环境配置
├── .env.staging // 测试环境配置
├── .eslintignore // 忽略语法检查
├── .eslintrc.js // eslint 配置项
├── .gitignore // git 忽略项
├── babel.config.js // babel.config.js
├── package.json // package.json
└── vue.config.js // vue.config.js

登录调用

(1)login.vue ->handleLogin()->validate 提交表单,校验,调用store/user.js->Login({ commit }, userInfo)
(2)全局管控store/user.js->Login(),调用api/login.js->login(username, password, code, uuid)
(3)请求接口api/login.js->login(),请求前utils/request.js->service.interceptors.request.use()请求拦截器
(4)请求拦截器service.interceptors.request.use(),设置token,utils/auth.js
(5)请求接口api/login.js->login()发起请求
(6)响应拦截器验证响应信息没有问题后,就会通过路由修改路径到index.vue

登录调用代码详解

【1】login.vue ->handleLogin()->validate 提交表单,校验,调用store/user.js->Login()

【-----------------login.vue--------------------】

loginForm: {
        username: "admin",
        password: "admin123",
        rememberMe: false,
        code: "",  
        uuid: ""
      },
 this.$store.dispatch("Login", this.loginForm).then(
                () => {
                        this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
                }
        ).catch(
                () => {
                 this.loading = false;
            if (this.captchaOnOff) {
              this.getCode();
            }
          }
          );
dispatch:含有异步操作,例如向后台提交数据,写法: this.$store.dispatch('action方法名',值)
commit:同步操作,写法:this.$store.commit('mutations方法名',值)
.then()方法:主要用于一个函数要用到另一个函数返回的值,
用户通过vue的router.push和router.replace来修改地址栏。同时监控地址栏。获取到对应组件,去配置信息里面寻找对应的页面显示

【2】全局管控store/user.js->Login(),调用api/login.js->login(username, password, code, uuid)

【-----------------store/user.js--------------------】

Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        
        login(username, password, code, uuid).then(res => {
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
          
        }).catch(error => {
          reject(error)
        })
      })
    },

【3】请求接口api/login.js->login(),请求前utils/request.js->service.interceptors.request.use()请求拦截器

【-----------------api/login.js--------------------】

// 登录方法
export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid
  }
  return request({
    url: '/login',
    method: 'post',
    data: data
  })
}

【4】请求拦截器service.interceptors.request.use(),设置token,utils/auth.js

【-----------------utils/request.js--------------------】

service.interceptors.request.use(config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  if (getToken() && !isToken) {
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
    let url = config.url + '?';
    for (const propName of Object.keys(config.params)) {
      const value = config.params[propName];
      var part = encodeURIComponent(propName) + "=";
      if (value !== null && typeof(value) !== "undefined") {
        if (typeof value === 'object') {
          for (const key of Object.keys(value)) {
            let params = propName + '[' + key + ']';
            var subPart = encodeURIComponent(params) + "=";
            url += subPart + encodeURIComponent(value[key]) + "&";
          }
        } else {
          url += part + encodeURIComponent(value) + "&";
        }
      }
    }
    url = url.slice(0, -1);
    config.params = {};
    config.url = url;
  }
  return config
}, error => {
    console.log(error)
    Promise.reject(error)
})

【-----------------utils/auth.js--------------------】

import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
  return Cookies.get(TokenKey)
}
export function setToken(token) {
  return Cookies.set(TokenKey, token)
}
export function removeToken() {
  return Cookies.remove(TokenKey)
}

【6】响应拦截器验证响应信息没有问题后,就会通过路由修改路径到index.vue

【-----------------router/index.js--------------------】

{
    path: '',
    component: Layout,
    redirect: 'index',
    children: [
      {
        path: 'index',
        component: (resolve) => require(['@/views/index'], resolve),
        name: '首页',
        meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true }
      }
    ]
  }

vue-router是基于路由和组件的路由用于设定访问路径,将路径和组件映射起来。在vue-router的单页面应用中, 页面的路径的改变就是组件的切换。

【-----------------相关技术--------------------】

【1】vue表单验证(form)validate

   默认校验
    (1)、required:true               必输字段
    (2)、remote:"remote-valid.jsp"   使用ajax方法调用remote-valid.jsp验证输入值
    (3)、email:true                  必须输入正确格式的电子邮件
    (4)、url:true                    必须输入正确格式的网址
    (5)、date:true                   必须输入正确格式的日期,日期校验ie6出错,慎用
    (6)、dateISO:true                必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22 只验证格式,不验证有效性
    (7)、number:true                 必须输入合法的数字(负数,小数)
    (8)、digits:true                 必须输入整数
    (9)、creditcard:true             必须输入合法的信用卡号
    (10)、equalTo:"#password"        输入值必须和#password相同
    (11)、accept:                    输入拥有合法后缀名的字符串(上传文件的后缀)
    (12)、maxlength:5                输入长度最多是5的字符串(汉字算一个字符)
    (13)、minlength:10               输入长度最小是10的字符串(汉字算一个字符)
    (14)、rangelength:[5,10]         输入长度必须介于 5 和 10 之间的字符串")(汉字算一个字符)
    (15)、range:[5,10]               输入值必须介于 5 和 10 之间
    (16)、max:5                      输入值不能大于5
    (17)、min:10                     输入值不能小于10
 默认消息
    messages: {
            required: "This field is required.",
            remote: "Please fix this field.",
            email: "Please enter a valid email address.",
            url: "Please enter a valid URL.",
            date: "Please enter a valid date.",
            dateISO: "Please enter a valid date (ISO).",
            dateDE: "Bitte geben Sie ein g眉ltiges Datum ein.",
            number: "Please enter a valid number.",
            numberDE: "Bitte geben Sie eine Nummer ein.",
            digits: "Please enter only digits",
            creditcard: "Please enter a valid credit card number.",
            equalTo: "Please enter the same value again.",
            accept: "Please enter a value with a valid extension.",
            maxlength: $.validator.format("Please enter no more than {0} characters."),
            minlength: $.validator.format("Please enter at least {0} characters."),
            rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
            range: $.validator.format("Please enter a value between {0} and {1}."),
            max: $.validator.format("Please enter a value less than or equal to {0}."),
            min: $.validator.format("Please enter a value greater than or equal to {0}.")
    },

【2】vuex状态管理

    一、状态管理(vuex)简介
            vuex是专为vue.js应用程序开发的状态管理模式。它采用集中存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex也集成刀vue的官方调试工具devtools extension,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。
    二、状态管理核心
            状态管理有5个核心,分别是state、getter、mutation、action以及module。分别简单的介绍一下它们:
            开始使用vuex,新建一个 sotre文件夹,分开维护 actions mutations getters

    1、state
             state为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等,只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态
    2、getter
            getter有点类似vue.js的计算属性,当我们需要从store的state中派生出一些状态,那么我们就需要使用getter,getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来,只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。
    3、mutation
            更改store中state状态的唯一方法就是提交mutation,就很类似事件。每个mutation都有一个字符串类型的事件类型和一个回调函数,我们需要改变state的值就要在回调函数中改变。我们要执行这个回调函数,那么我们需要执行一个相应的调用方法:store.commit。
    4、action
            action可以提交mutation,在action中可以执行store.commit,而且action中可以有任何的异步操作。在页面中如果我们要嗲用这个action,则需要执行store.dispatch

【3】Token

    1、Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
    2、Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
    3、使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

以上是关于若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出的主要内容,如果未能解决你的问题,请参考以下文章

若依管理系统(前后端分离版)部署并实现持续发布

若依配置教程若依前后端分离版部署到服务器Nginx(Windows版)

RuoYi(若依开源框架)-前后端分离版-前端流程简单分析

RuoYi(若依开源框架)-前后台分离版-后端流程简单分析

Java——Linux使用Docker部署若依前后端分离版保姆级教程

Ruoyi-Vue若依前后端分离版 代码生成