一、数据字典介绍
1、介绍
何为数据字典?
数据字典就是管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。
一句话就是数据以官方为准,我们需要按照官方发布的数据进行维护数据,其他时间数据是固定的
2、预期效果
3、数据库字典字段设计
parent_id:
上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据
name:名称,
例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称
value:值,
例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值
dict_code:
编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据
4、数据接口分析
数据字典是树形展示,由于数据比较多,我们使用树形数据与el-ui的懒加载方式,获取数据和展示数据,其他就是数据的增删改查,只有当我们点击显示下一级数据时,我们才会加载下一级的数据。
二、数据字典服务端开发
1、搭建service_cmn模块来完成数据字典的操作
搭建过程还是和service_hosp一样的,创建一个普通的maven工程,选择父工程为service模块,修改我们pom配置文件,创建我们的启动类,创建对应的配置文件,添加对应的配置信息
2、数据字典列表的获取
(1)分析
因为我们的前端使用el-ui 搭建字典列表的树形结构,我们不需要做过多的开发,对前端使用的组件需求数据分析可以知道,我们需要根据前端传递过来的id值来获取对应的此id下的子节点,而且还需一个特别的属性hasChildren=?
(2)model对象创建
注意:hasChildren属性我们是可以存在数据库的,只是前端树形组件需要这个数据,我们加上注解 @TableField(exist = false)表示数据库可以不存在此字段
@Data
@ApiModel(description = "数据字典")
@TableName("dict")
public class Dict {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map<String, Object> param = new HashMap<>();
@ApiModelProperty(value = "上级id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty(value = "名称")
@TableField("name")
private String name;
@ApiModelProperty(value = "值")
@TableField("value")
private String value;
@ApiModelProperty(value = "编码")
@TableField("dict_code")
private String dictCode;
@ApiModelProperty(value = "是否包含子节点")
@TableField(exist = false)
private boolean hasChildren;
// exist = false 意思是数据库可以不存在此字段
}
(3)mapper接口
创建mapper接口,继承BaseMapper
@Repository
public interface DictMapper extends BaseMapper<Dict> {
}
(4)service层
创建接口,继承mp提供的IService接口
public interface DictService extends IService<Dict> {
List<Dict> findChildData(Long id);
void exportDictData(HttpServletResponse response);
void importDictData(MultipartFile file);
}
创建实现service接口实现类继承mp的ServiceImpl
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
/**
* 添加数据到缓存中
*
* @param id
* @return
*/
@Override
@Cacheable(value = "dict", keyGenerator = "keyGenerator")
public List<Dict> findChildData(Long id) {
// 构造条件
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id", id);
// 查询数据
List<Dict> dicts = baseMapper.selectList(wrapper);
// 修改是否存在子节点数据值
for (Dict dict : dicts) {
dict.setHasChildren(isChildren(dict.getId()));
}
return dicts;
}
}
(5)controller
@Api(tags = "数据字典模块")
@RestController
@RequestMapping("/admin/cmn/dict")
@CrossOrigin
public class DictController {
@Autowired
private DictService dictService;
/**
* 根据数据id查询子数据列表
*
* @param id 数据Id
* @return
*/
@ApiOperation("根据数据id查询子数据列表")
@GetMapping("/findChildData/{id}")
public Result findChildData(@PathVariable("id") Long id) {
List<Dict> dicts = dictService.findChildData(id);
return Result.ok(dicts);
}
}
3、字典的导出工程实现
我们使用Alibaba的EasyExcel工具进行完成数据的导入和导出。
(1)导入依赖
因为只有service_cmn 使用此依赖,所以我们只需要把依赖导入到service_cmn 模块下
<dependencies>
<dependency>
<!--alibaba excel操作-->
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
(2)按照EasyExcel规范创建model类
为啥不直接使用dict实体类,反而又创建一个实例类呢,本着,不同的业务使用不同的实体类,解耦合,后期修改不影响其他实体类的效果
@Data
public class DictEeVo {
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "上级id" ,index = 1)
private Long parentId;
@ExcelProperty(value = "名称" ,index = 2)
private String name;
@ExcelProperty(value = "值" ,index = 3)
private String value;
@ExcelProperty(value = "编码" ,index = 4)
private String dictCode;
}
(3)导出字典数据接口开发
1、controller
因为导出是一个下载动作,我们需要报我们的数据数据导出到excel中,所以我们response对象来完成下载操作。
/**
* 实现下载功能,
* 我们需要HttpServletResponse来完成下载功能
* 使用流的形式返回给浏览器
*
* @param response
*/
@ApiOperation("导出数据字典")
@GetMapping("/exportData")
public void exportData(HttpServletResponse response) {
dictService.exportDictData(response);
}
2、service
其实就是一个下载操作
- 完成下载信息的设置
- 读取数据
- 数据库数据转换为导出字典数据
- 使用EasyExcel完成写文件的操作
- 把文件写入到下载流中。
/**
* 实现字典数据导出功能
*
* @param response
*/
@Override
public void exportDictData(HttpServletResponse response) {
try {
// 1、设置下载的信息
// 1.1、再回传前,通过响应头告诉客户端返回的数据类型excel类型
response.setContentType("application/vnd.ms-excel");
// 1.2、设置下载的文件名字,且对中文进行utf-8编码
String fileName = URLEncoder.encode("数据字典.xlsx", "UTF-8");
// 1.3、设置响应头的信息,attachment(附件下载); fileName=(文件名)
response.setHeader("Content-Disposition", "attachment; fileName=" + fileName);
// 2、准备响应的数据流,使用response.getOutputStream(流数据);把流输出浏览器
// 因为我们需要读取数据,封装数据,所以我们需要先对数据进行处理
// 获取数据库字典信息
List<Dict> dicts = baseMapper.selectList(null);
// 创建写文件字典对象,vo上标注注解@ExcelProperty,专门用来导入和导出excel文件
List<DictEeVo> dictEeVos = new ArrayList<>();
// 把我们的dict对象,转换为dictEevo对象
for (Dict dict : dicts) {
// 创建需要转换的vo对象
DictEeVo dictEeVo = new DictEeVo();
// 调用BeanUtils.copyProperties转换,求实就是获取属性,设置属性的操作。
BeanUtils.copyProperties(dict, dictEeVo);
// 添加到集合中
dictEeVos.add(dictEeVo);
}
// 3、把EasyExcel写文件写入到response.getOutputStream()中,直接响应给客户端
EasyExcel.write(response.getOutputStream(), DictEeVo.class)
.sheet("dict").doWrite(dictEeVos);
} catch (IOException e) {
e.printStackTrace();
}
}
4、导入字典数据接口开发
其实就是上传Excel文件,使用EasyExcel读取文件(一行一行的读取数据),然后添加到数据库。但是想要使用EasyExcel读取Excel文件,需要我们使用按照EasyExcel规范实现监听器,来完成一行行的读取数据
(1)实现监听器(具体对读取数据的操作)
public class DictListener extends AnalysisEventListener<DictEeVo> {
private DictMapper dictMapper;
// 调用Mapper测试插入数据
public DictListener(DictMapper dictMapper) {
this.dictMapper = dictMapper;
}
/**
* 监听读取excel文件
* 一行一行的读取
*
* @param data
* @param context
*/
@Override
public void invoke(DictEeVo data, AnalysisContext context) {
Dict dict = new Dict();
BeanUtils.copyProperties(data, dict);
dictMapper.insert(dict);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
}
(2)controller
/**
* 导入字典数据
*
* @param file
* @return
*/
@PostMapping("/importData")
public Result importData(@RequestPart MultipartFile file) {
dictService.importDictData(file);
return Result.ok();
}
(3)service
监听器自动监听我们的读数据的操作。
/**
* 导入字典数据
* allEntries=true 刷新我们的缓存
*
* @param file
*/
@Override
@CacheEvict(value = "dict", allEntries = true)
public void importDictData(MultipartFile file) {
// 直接读取我们上长传文件的流,对读取的数据进行处理在监听器里面实现
try {
EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictListener(baseMapper))
.sheet()
.doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
三、前端模块开发
1、字典数据列表展示
(1)添加路由
因为这个路由只有一个子节点,所以我们需要显示父节点数据的话,需要我们指定一个属性alwaysShow: true,让他一直显示
{
path: '/cmn',
component: Layout,
redirect: '/cmn/list',
name: '数据字典',
meta: {title: '数据管理', icon: 'example'},
// 表示总是显示这个路由信息,即使只有一个子标签
alwaysShow: true,
children: [
{
path: 'list',
name: '数据字典列表',
component: () => import('@/views/dict/list'),
meta: {title: '数据字典', icon: 'table'}
}
]
},
(2)定义api
使用es6的模板字符串参数拼接
dictList(id) {
return request({
// 后端服务接口
url: `/admin/cmn/dict/findChildData/${id}`,
method: 'get'
})
}
(3)使用el-ui的树形结构组件
<template>
<div class="app-container">
<el-table
:data="list"
style="width: 100%"
row-key="id"
border
lazy
:load="getChildrens"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column label="名称" width="230" align="left">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="编码" width="220">
<template slot-scope="{row}">
{{ row.dictCode }}
</template>
</el-table-column>
<el-table-column label="值" width="230" align="left">
<template slot-scope="scope">
<span>{{ scope.row.value }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center">
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
(4)定义页面vue脚本
通过对el-ui树形组件的分析我们可以知道我们需要加载哪些数据,以及创建哪些方法,el-ui都是规定好的数据,以及绑定的事件函数等等。
<script>
import dict from '@/api/dict'
export default {
// 定义变量和参数
data() {
return {
list: [], // 数据字典列表数组
dialogImportVisible: false // 控制上传文件弹出层是否弹出
}
},
created() {
// 获取根节点数据
this.getDictList(1)
},
methods: {
// 导入数据
importData() {
// 打开弹出层
this.dialogImportVisible = true
},
// 上传成功调用的函数
onUploadSuccess() {
// 关闭弹出层
this.dialogImportVisible = false
// 刷新数据(即重新获取数据)
this.getDictList(1)
},
// 获取字典列表结合
getDictList(id) {
dict.dictList(id)
.then(response => {
this.list = response.data
})
},
// el-ui自动调用获取字节点数据
// 参数啥都都是el-ui自动封装好的,我们不需要过多操作
getChildrens(tree, treeNode, resolve) {
// 获取该节点下的子节点数据
dict.dictList(tree.id).then(response => {
resolve(response.data)
})
}
}
}
</script>
2、字典数据导出
(1)列表页面添加导出按钮使用a标签
直接使用a标签,直接直接访问我们后端接口获取下载数据。
<a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank">
<el-button type="text"><i class="fa fa-plus"/> 导出</el-button>
</a>
3、字典数据导入操作
(1)页面添加导入组件
<el-button type="text"@click="importData"><i class="fa fa-plus"/> 导入</el-button>
(2)添加弹出层友好提示(包括上传文件操作)
就是bootstart的拟态框效果,其中自动封装上传文件的操作。
<!-- 上传文件弹出层 el-ui 提供的
:visible.sync="dialogImportVisible" 双向绑定dialogImportVisible=flase不展示,true展示
-->
<el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px">
<el-form label-position="right" label-width="170px">
<el-form-item label="文件">
<!-- 上传的核心代码 multiple=false不支持多文件上传,反之,on-success上传成功调用的函数-->
<el-upload
:multiple="false"
:on-success="onUploadSuccess"
:action="'http://localhost:8202/admin/cmn/dict/importData'"
class="upload-demo">
<!--提示信息-->
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传xls文件,且不超过500kb</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogImportVisible = false">
取消
</el-button>
</div>
</el-dialog>
创建弹出层需要的数据,以及绑定的函数
dialogImportVisible: false // 控制上传文件弹出层是否弹出
// 导入数据
importData() {
// 打开弹出层
this.dialogImportVisible = true
},
// 上传成功调用的函数
onUploadSuccess() {
// 关闭弹出层
this.dialogImportVisible = false
// 刷新数据(即重新获取数据)
this.getDictList(1)
},