service-core
模块
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
dependency>
<dependency>
<groupId>org.apache.xmlbeansgroupId>
<artifactId>xmlbeansartifactId>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
创建dto
包
ExcelDictDTO.java
package com.indi.srb.core.pojo.dto;
@Data
public class ExcelDictDTO {
// ExcelProperty注解
// 写入的时候,会写到Excel的表头,
// 读取的时候,会根据表头的名称,自动的读到对应的属性上
@ExcelProperty("id")
private Long id;
@ExcelProperty("上级id")
private Long parentId;
@ExcelProperty("名称")
private String name;
@ExcelProperty("值")
private Integer value;
@ExcelProperty("编码")
private String dictCode;
}
DictMapper.java
void insertBatch(List<ExcelDictDTO> list);
DictMapper.xml
<insert id="insertBatch">
insert into dict(
id,
parent_id,
name,
value,
dict_code
) values
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id},
#{item.parentId},
#{item.name},
#{item.value},
#{item.dictCode}
)
foreach>
insert>
在com.indi.srb.core
包下面创建listener
包
ExcelDictDTOListener.java
package com.indi.srb.core.listener;
@Slf4j
@NoArgsConstructor
public class ExcelDictDTOListener extends AnalysisEventListener<ExcelDictDTO> {
private DictMapper dictMapper;
List<ExcelDictDTO> list = new ArrayList<>(); // 数据列表
// 每5条记录存储一次数据
private static final int BATCH_COUNT = 5;
public ExcelDictDTOListener(DictMapper dictMapper) {
this.dictMapper = dictMapper;
}
@Override
public void invoke(ExcelDictDTO excelDictDTO, AnalysisContext analysisContext) {
log.info("解析一条数据:{}", excelDictDTO);
// 将数据存入数据列表
list.add(excelDictDTO);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}
private void saveData() {
log.info("{}条数据被存储到数据库", list.size());
// 调用mapper层的save方法:save list对象
dictMapper.insertBatch(list);
log.info("{}条数据存储到数据库成功", list.size());
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 当最后剩余的记录数不足BATCH_COUNT时,最终一次性存入
saveData();
log.info("全部数据解析完成!");
}
}
DictService.java
void importData(InputStream inputStream);
DictServiceImpl.java
// 一旦中途导入失败,直接回滚数据
@Transactional(rollbackFor = Exception.class)
@Override
public void importData(InputStream inputStream) {
EasyExcel.read(inputStream, ExcelDictDTO.class, new ExcelDictDTOListener(baseMapper)).sheet().doRead();
log.info("Excel导入成功");
}
admin
包下面创建AdminDictController.java
@Api(tags = "数据字典管理")
@RestController
@RequestMapping("/admin/core/dict")
@Slf4j
@CrossOrigin
public class AdminDictController {
@Resource
DictService dictService;
@ApiOperation("Excel数据的批量导入")
@PostMapping("/import")
public R batchImport(
@ApiParam(value = "Excel数据字典文件", required = true)
@RequestParam("file") MultipartFile file){
try {
InputStream inputStream = file.getInputStream();
dictService.importData(inputStream);
return R.ok().setMessage("数据字典批量导入成功");
} catch (IOException e) {
throw new BusinessException(ResponseEnum.UPLOAD_ERROR, e);
}
}
}
src/router/index.js
{
path: '/core',
component: Layout,
redirect: '/core/dict/list',
name: 'coreDict',
meta: {
title: '系统设置', icon: 'el-icon-setting' },
alwaysShow: true,
children: [
{
path: 'dict/list',
name: '数据字典',
component: () => import('@/views/core/dict/list'),
meta: {
title: '数据字典' }
}
]
},
src/views/core/dict/list.vue
<template>
<div class="app-container">
<div style="margin-bottom: 10px;">
<el-button
@click="dialogVisible = true"
type="primary"
size="mini"
icon="el-icon-download"
>
导入Excel
el-button>
div>
<el-dialog title="数据字典导入" :visible.sync="dialogVisible" width="30%">
<el-form>
<el-form-item label="请选择Excel文件">
<el-upload
:auto-upload="false"
:multiple="false"
:limit="1"
:on-exceed="fileUploadExceed"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:action="BASE_API + '/admin/core/dict/import'"
name="file"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
>
<el-button size="small" type="primary">点击上传el-button>
el-upload>
el-form-item>
el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">
取消
el-button>
div>
el-dialog>
div>
template>
<script>
export default {
// 定义数据
data() {
return {
dialogVisible: false, //文件上传对话框是否显示
BASE_API: process.env.VUE_APP_BASE_API //获取后端接口地址
}
},
methods: {
// 上传多于一个文件时
fileUploadExceed() {
this.$message.warning('只能选取一个文件')
},
// 与服务器通信成功的回调
fileUploadSuccess(response) {
if (response.code === 0) {
// 业务成功
this.$message.success('数据导入成功')
this.dialogVisible = false
} else {
// 业务失败
this.$message.error(response.message)
}
},
// 与服务器通信失败的回调
fileUploadError(error) {
this.$message.error('数据导入失败')
}
}
}
script>
DictService.java
List<ExcelDictDTO> dictListData();
DictServiceImpl.java
@Override
public List<ExcelDictDTO> dictListData() {
List<Dict> dictList = baseMapper.selectList(null);
ArrayList<ExcelDictDTO> excelDictDTOList = new ArrayList<>(dictList.size());
dictList.forEach(dict -> {
ExcelDictDTO excelDictDTO = new ExcelDictDTO();
BeanUtils.copyProperties(dict,excelDictDTO);
excelDictDTOList.add((excelDictDTO));
});
return excelDictDTOList;
}
AdminDictController.java
@ApiOperation("Excel数据的批量导出")
@GetMapping("/export")
public void export(HttpServletResponse response) {
try {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("mydict", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=mydict.xlsx");
// 主要是这个地方
EasyExcel.write(response.getOutputStream(), ExcelDictDTO.class).sheet().doWrite(dictService.dictListData());
} catch (IOException e) {
//EXPORT_DATA_ERROR(104, "数据导出失败"),
throw new BusinessException(ResponseEnum.EXPORT_DATA_ERROR, e);
}
}
<el-button
@click="exportData"
type="primary"
size="mini"
icon="el-icon-download"
>
导出Excel
el-button>
在methods中添加Excel数据导出方法
// Excel数据导出
exportData() {
// 因为后端的导出接口是无刷新的,我们需要要让浏览器主动刷新,
// 所以就不能是使用axios发送普通请求,需要使用下面这种方式发送请求
window.location.href = this.BASE_API + '/admin/core/dict/export'
}
Dict.java
中添加属性
@ApiModelProperty(value = "是否包含子节点")
@TableField(exist = false) // 在数据库表中忽略此列
private boolean hasChildren;
DictService.java
List<Dict> listByParentId(Long parentId);
DictServiceImpl.java
@Override
public List<Dict> listByParentId(Long parentId) {
QueryWrapper<Dict> queryWrapper = new QueryWrapper<Dict>().eq("parent_id",parentId);
List<Dict> dictList = baseMapper.selectList(queryWrapper);
// 填充hasChilderen字段
dictList.forEach(dict -> {
boolean hasChildren = this.hasChildren(dict.getId());
dict.setHasChildren(hasChildren);
});
return dictList;
}
/**
* 判断当前id所在的节点是否有子节点
*/
private boolean hasChildren(Long id) {
QueryWrapper<Dict> queryWrapper = new QueryWrapper<Dict>().eq("parent_id",id);
Integer count = baseMapper.selectCount(queryWrapper);
if(count.intValue() > 0) {
return true;
}
return false;
}
AdminDictController.java
/**
* 方案二:延迟加载
* 不需要后端返回数据中包含嵌套数据,但是要定义布尔属性hasChildren,表示当前节点是否包含子数据
* 如果hasChildren为true,就表示当前节点包含子数据
* 如果hasChildren为false,就表示当前节点不包含子数据
* 如果当前节点包含子数据,那么点击当前节点的时候,就需要通过load方法加载子数据
*/
@ApiOperation("根据上级id获取子节点数据列表")
@GetMapping("/listByParentId/{parentId}")
public R listByParentId(
@ApiParam(value = "上级节点id", required = true)
@PathVariable Long parentId) {
List<Dict> dictList = dictService.listByParentId(parentId);
return R.ok().data("list", dictList);
}
创建 src/api/core/dict.js
import request from '@/utils/request'
export default {
listByParentId(parentId) {
return request({
url: `/admin/core/dict/listByParentId/${
parentId}`,
method: 'get'
})
}
}
src/views/core/dict/list.vue
放到之前写的闭合标签下面
<el-table :data="list" border row-key="id" lazy :load="load">
<el-table-column label="名称" align="left" prop="name" />
<el-table-column label="编码" prop="dictCode" />
<el-table-column label="值" align="left" prop="value" />
el-table>
先导入dictApi
import dictApi from '@/api/core/dict'
data中定义数据字典列表
list: [] //数据字典列表
methods中添加新方法
// 调用api层获取1级菜单数据
fetchData() {
dictApi.listByParentId(1).then(response => {
this.list = response.data.list
})
},
// 延迟加载子节点的方法
load(tree, treeNode, resolve) {
dictApi.listByParentId(tree.id).then(response => {
// 负责将子节点数据放到展开的列表中
resolve(response.data.list)
})
}
初始化调用
created() {
this.fetchData()
},
数据导入后刷新页面的数据列表