springboot2.x+security+vue2.x后台管理框架---字典管理(四)
字典管理
在管理系统中很多地方都会用到下拉表,但是通常的下拉列表中的数据都是写死的,复用性太低了,字典管理可以达到前端可以多次利用,实现效果如下图:
一、数据库构建
-- longzy.sys_dict definition
CREATE TABLE `sys_dict` (
`dictid` int(20) NOT NULL AUTO_INCREMENT COMMENT '字典id',
`type` varchar(100) NOT NULL COMMENT '类型',
`code` varchar(100) NOT NULL COMMENT '码值code',
`label` varchar(100) NOT NULL COMMENT '码值名称',
`effective` varchar(1) NOT NULL COMMENT '有效状态',
PRIMARY KEY (`dictid`)
) ENGINE=InnoDB AUTO_INCREMENT=1000000012 DEFAULT CHARSET=utf8mb4 COMMENT='字典';
二、字典管理前端及后台构建
1、前端效果
2、自定义ElementUI 的el-table
<template>
<el-table
:data="data"
:height="height"
:stripe="stripe"
:border="border"
size="small"
:fit="fit"
:highlight-current-row="highlightCurrentRow"
:tooltip-effect="tooltipEffect"
:header-cell-style="{background:'#eef1f6',color:'#606266'}"
:selection="selection"
:row-key="rowKey"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
@selection-change="selectionChange"
@row-click="rowClick"
>
<el-table-column v-if="selection"
type="selection"
width="100"
align="center">
</el-table-column>
<el-table-column v-for="item, index in columns"
:key="index"
:type="item.type"
:index="item.indexMethod"
:label="item.label"
:prop="item.prop"
:width="item.width"
:fixed="item.fixed"
:align="item.align"
:show-overflow-tooltip="true"
>
<template v-if="item.slot" #default="scope">
<slot :name="item.prop" :scope="scope"></slot>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default{
name: 'myTable',
props:{
// 表头
columns: {
type: Array,
default: () => []
},
// 表格数据
data: {
type: Array,
default: () => []
},
// Table 的高度,默认为自动高度
height: {
type: String,
default: ''
},
// 是否为斑马纹 table
stripe: {
type: Boolean,
default: false
},
// 是否带有纵向边框
border: {
type: Boolean,
default: false
},
// 列的宽度是否自撑开
fit: {
type: Boolean,
default: true
},
// 是否要高亮当前行
highlightCurrentRow: {
type: Boolean,
default: false
},
// tooltip effect 属性 dark/light
tooltipEffect: {
type: String,
default: ''
},
// 是否显示多选
selection: {
type: Boolean,
default: false
},
// 树形结构id
rowKey: {
type: String,
default: ''
},
},
methods: {
// 当选择项发生变化时会触发该事件 表格checkbox 选择行回调
selectionChange(val) {
this.$emit('selectionChange', val)
},
// 当某一行被点击时会触发该事件
rowClick(row, column, event){
this.$emit('rowClick', row, column, event)
}
}
}
</script>
<style lang="less" scoped>
</style>
3、dictManagement.vue页面
<template>
<div class="app-content">
<div class="search">
<el-input v-model="searchInfo" placeholder="请输入字典类型、字段名称"/>
<el-button type="primary" icon="el-icon-search" @click="queryDictList">搜索</el-button>
</div>
<div class="op-btn">
<el-button type="primary" size="small" icon="el-icon-circle-plus" @click="editVisible = true, rowData = {}" >新增</el-button>
<el-dropdown @command="hanldeEffective">
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
icon="el-icon-success"
command="1"
:disabled="multipleSelection.length > 0 ? false : true">批量启用</el-dropdown-item>
<el-dropdown-item
icon="el-icon-error"
command="2"
:disabled="multipleSelection.length > 0 ? false : true"
divided >批量禁用</el-dropdown-item>
<el-dropdown-item
@click.native="handleDelete"
icon="el-icon-remove"
:disabled="multipleSelection.length > 0 ? false : true"
divided>批量删除</el-dropdown-item>
</el-dropdown-menu>
<el-button type="primary" size="small">
批量操作<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
</el-dropdown>
</div>
<my-table :data="dictData"
:columns="columns"
height="580px"
:selection="true"
tooltipEffect="dark"
:highlightCurrentRow="true"
@selectionChange="handleSelectionChange"
>
<!-- 有效状态 -->
<template slot="effective" slot-scope="{scope}">
<el-tag
:type="scope.row.effective === '1' ? 'success' : 'danger'"
disable-transitions>{{$store.getters.getDictLabelByType('EFFECTIVE', scope.row.effective)}}</el-tag>
</template>
<!-- 操作 -->
<template slot="operate" slot-scope="{scope}">
<el-button type="text" size="small" @click="fnEdit(scope.row)">编辑</el-button>
<el-dropdown>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="hanldeEffective('1', scope.row)" >启用</el-dropdown-item>
<el-dropdown-item @click.native="hanldeEffective('2', scope.row)" divided >禁用</el-dropdown-item>
<el-dropdown-item @click.native="handleDelete(scope.row)" divided>删除</el-dropdown-item>
</el-dropdown-menu>
<span class="el-dropdown-link">
更多<i class="el-icon-arrow-down el-icon--right"></i>
</span>
</el-dropdown>
</template>
</my-table>
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[10, 20, 50, 100]"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
<!-- 新增/编辑 -->
<edit-dict :visible="editVisible" :rowData="rowData" @close="editVisible = false" @queryDictList="queryDictList"></edit-dict>
</div>
</template>
<script>
import $api from './api/index'
import editDict from './part/editDict'
const columns = [
{label: '字典类型', prop: 'type', align: 'center'},
{label: '码值名称', prop: 'label', align: 'center'},
{label: '码值', prop: 'code', align: 'center'},
{label: '有效状态', prop: 'effective', align: 'center', slot: 'effective'},
{label: '操作', prop: 'operate', align: 'center', slot: 'operate'}
]
export default{
name: 'dictManagement',
components: {editDict},
data() {
return{
editVisible: false,
multipleSelection: [], // 多选
columns,
pageNum: 1, // 页码
pageSize: 10, // 每页条数
total: 0, // 总条数
searchInfo: '',
dictData: [],
rowData: {}
}
},
mounted() {
this.queryDictList()
},
methods: {
// 编辑
fnEdit(row){
this.editVisible = true
this.rowData = row
},
// 页码变化方法
handleCurrentChange(current){
this.pageNum = current
this.queryDictList()
},
// pageSize 每页条数改变方法
handleSizeChange(size){
this.pageSize = size
this.queryDictList()
},
// 填充表格数据
queryDictList(){
let param = {
searchInfo: this.searchInfo,
pageNum: this.pageNum,
pageSize: this.pageSize
}
$api.queryDictList(param).then(res => {
if (res.data.code == 200){
var data = res.data.data
this.pageNum = data.pageNum
this.pageSize = data.pageSize
this.total = data.total
this.dictData = data.list
}
})
},
// type 1:批量启用 2:批量禁用
hanldeEffective(type, row){
let dictIds = []
// 判断是否批量操作
if (row.dictId){
if (type == "1"){
if (row.effective == "1"){
this.$message({
showClose: true,
message: '该记录已启用,请勿重复操作.',
type: 'error',
})
return
}
}else {
if (row.effective != "1"){
this.$message({
showClose: true,
message: '该记录已禁用,请勿重复操作.',
type: 'error',
})
return
}
}
dictIds.push(row.dictId)
}else {
this.multipleSelection.forEach(e => {
dictIds.push(e.dictId)
})
}
$api.updateEffective({type: type, dictIds: dictIds.join(",")}).then(res => {
if (res.data.code == 200){
this.queryDictList()
this.multipleSelection = []
this.$message({
showClose: true,
message: type == '1' ? '启用成功.' : '禁用成功.',
type: 'success',
})
}
})
},
// 多选
handleSelectionChange(val){
this.multipleSelection = val
},
// 删除
handleDelete(row){
let dictIds = []
if (row.dictId){
dictIds.push(row.dictId)
}else {
this.multipleSelection.forEach(e => {
dictIds.push(e.dictId)
})
}
this.$confirm('确认删除该记录?',{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
$api.deleteDictByDictIds({dictIds: dictIds.join(',')}).then(res => {
this.queryDictList()
this.multipleSelection = []
this.$message({
showClose: true,
message: '删除成功.',
type: 'success',
})
})
})
}
}
}
</script>
<style lang="less" scoped>
.search {
text-align: center;
margin-bottom: 40px;
.el-input{
width: 300px;
}
}
.op-btn{
float: right;
margin-right: 20px;
margin-bottom: 5px;
}
.el-button{
margin-right: 10px;
}
.el-pagination {
float: right;
margin: 10px;
}
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
}
.el-icon-arrow-down {
font-size: 12px;
}
</style>
4、编辑字典editDict.vue页面
<template>
<el-drawer
:title="rowData.dictId ? '编辑字典' : '新增字典'"
:visible="visible"
size="25%"
:before-close="closeDrawer"
:destroy-on-close="true"
>
<el-form ref="formData" :rules="formRules" :model="formData" class="drawer__content" label-width="120px">
<el-form-item label="字典类型" prop="type">
<el-input v-model.trim="formData.type" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="码值名称" prop="label">
<el-input v-model.trim="formData.label" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="码值code" prop="code">
<el-input v-model.trim="formData.code" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="有效状态" prop="effective" >
<el-switch v-model="formData.effective"></el-switch>
</el-form-item>
</el-form>
<div class="drawer__footer">
<el-button @click="resetForm('formData')">重 置</el-button>
<el-button type="primary" @click="submitForm('formData')">确 定</el-button>
</div>
</el-drawer>
</template>
<script>
import $api from '../api/index'
export default {
name: 'editDict',
props: ['visible', 'rowData'],
data(){
return {
drawer: false,
formData: {},
formRules: {
type: [
{ required: true, message: '请输入字典类型', trigger: 'blur'}
],
label: [
{ required: true, message: '请输入码值名称', trigger: 'blur'}
],
code: [
{ required: true, message: '请输入码值code', trigger: 'blur'}
]
}
}
},
watch: {
visible(val){
if (val){
this.setFormValue()
}
}
},
methods: {
// 表单数据
setFormValue(){
const {dictId, type, label, code, effective} = this.rowData
if(dictId == undefined){
this.formData = {
effective: true
}
}else {
this.formData = {
dictId: dictId,
type: type,
label: label,
code: code,
effective: effective == '1' ? true : false
}
}
},
// 编辑/新增保存
submitForm(formName){
this.$refs[formName].validate((valid) => {
if (valid){
this.formData.effective = this.formData.effective ? '1' : '0'
// 新增
if(this.formData.dictId == undefined){
$api.addDict(this.formData).then(res => {
if (res.data.code == 200){
this.closeDrawer()
this.$emit('queryDictList')
this.$message({
showClose: true,
message: res.data.message,
type: 'success',
})
}
})
}else { // 编辑
$api.editDict(this.formData).then(res => {
if (res.data.code == 200){
this.$emit('queryDictList')
this.closeDrawer()
this.$message({
showClose: true,
message: res.data.message,
type: 'success',
})
}
})
}
}
})
},
// 重置表单
resetForm(formName){
this.$refs[formName].resetFields();
},
// 关闭
closeDrawer(){
this.formData = {}
this.resetForm('formData')
this.$emit('close')
}
}
}
</script>
<style lang="less" scoped>
/deep/ .el-drawer__header{
padding: 20px;
border-bottom: 1px solid rgba(0,21,41,0.08);
}
.drawer__content {
width: 90%;
}
.drawer__footer{
position: absolute;
bottom: 20px;
left: 25px;
.el-button{
width: 150px;
margin-right: 15px;
}
}
</style>
5、调用js请求后台方法
import request from '@/axios/request';
const api = {
// 新增
addDict(data){
return request.post("dict/addDict", data, true)
},
// 编辑
editDict(data){
return request.post("dict/editDict", data, true)
},
// 查询字典存缓存
queryDictCache(data){
return request.post("/dict/queryDictCache", data)
},
// 查询字典列表
queryDictList(data){
return request.post("/dict/queryDictList", data, true)
},
// 删除
deleteDictByDictIds(data){
return request.post("/dict/deleteDictByDictIds", data, true)
},
// 批量禁用启用
updateEffective(data){
return request.post('/dict/updateEffective', data, true)
}
}
export default api
6、后台实现前端调用方法
controller
package com.longzy.component.dict.controller;
import com.github.pagehelper.PageInfo;
import com.longzy.component.dict.entity.SysDictVo;
import com.longzy.component.dict.service.read.SysDictReadService;
import com.longzy.component.dict.service.write.SysDictWriteService;
import com.longzy.error.BusinessException;
import com.longzy.common.page.PageParam;
import com.longzy.common.response.CommonReturnType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Desc: 字典
* @Packge: com.longzy.component.dict.controller
* @Author: longzy
* @Date: 2022/9/16 10:03
*/
@Api(tags = "字典模块")
@RestController
@RequestMapping("dict")
public class SysDictController {
@Autowired
private SysDictReadService sysDictReadService;
@Autowired
private SysDictWriteService sysDictWriteService;
/**
* 查询字典列表
* @param searchInfo 类型或名称
* @param pageParam 分页参数
* @return
*/
@ApiOperation(value = "查询字典列表", notes = "根据类型或名称查询")
@PostMapping("queryDictList")
public CommonReturnType queryDictList(String searchInfo, @Validated PageParam pageParam){
PageInfo<SysDictVo> page = sysDictReadService.queryDictList(searchInfo, pageParam);
return CommonReturnType.success("操作成功", page);
}
@ApiOperation(value = "新增字典")
@PostMapping("addDict")
public CommonReturnType addDict(SysDictVo dictVo) {
int count = sysDictWriteService.addDict(dictVo);
if (count < 1){
throw new BusinessException("新增字典失败.");
}
return CommonReturnType.success("新增字典成功.");
}
@ApiOperation(value = "编辑字典")
@PostMapping("editDict")
public CommonReturnType editDict(SysDictVo dictVo) {
int count = sysDictWriteService.editDict(dictVo);
if (count < 1){
throw new BusinessException("编辑字典失败.");
}
return CommonReturnType.success("编辑字典成功.");
}
@ApiOperation(value = "删除字典", notes = "根据id删除")
@PostMapping("deleteDictByDictIds")
public CommonReturnType deleteDictByDictIds(String dictIds) {
sysDictWriteService.deleteDictByDictIds(dictIds);
return CommonReturnType.success("删除成功.");
}
/**
* 批量处理状态
* @param type 1启用 2禁用
* @param dictIds id列表
* @return
*/
@ApiOperation(value = "修改字段状态", notes = "根据type判断启用还是禁用")
@PostMapping("updateEffective")
public CommonReturnType updateEffective(String type, String dictIds){
sysDictWriteService.updateEffective(type, dictIds);
return CommonReturnType.success("操作成功.");
}
}
service
@Override
public PageInfo<SysDictVo> queryDictList(String searchInfo, PageParam pageParam) {
PageHelper.startPage(pageParam.getPageNum(), pageParam.getPageSize());
List<SysDictVo> dictList = sysDictReadMapper.queryDictList(searchInfo);
return new PageInfo<>(dictList);
}
@Override
public int addDict(SysDictVo dictVo) {
// 去重
if (checkDictExist(dictVo)){
throw new BusinessException("字典类型和码值已存在,请重新输入.");
}
SysDictPo dictPo = ConvertUtils.convert(dictVo, new SysDictPo());
if (!StringUtils.isEmpty(dictPo.getType())){
dictPo.setType(dictPo.getType().toUpperCase());
}
return sysDictWriteMapper.addDict(dictPo);
}
@Override
public int editDict(SysDictVo dictVo) {
// 去重
if (checkDictExist(dictVo)){
throw new BusinessException("字典类型和码值已存在,请重新输入.");
}
SysDictPo dictPo = ConvertUtils.convert(dictVo, new SysDictPo());
if (!StringUtils.isEmpty(dictPo.getType())){
dictPo.setType(dictPo.getType().toUpperCase());
}
return sysDictWriteMapper.editDict(dictPo);
}
@Override
public void deleteDictByDictIds(String dictIds) {
int count = sysDictWriteMapper.deleteDictByDictIds(ConvertUtils.integerToList(dictIds));
if (count < 1){
throw new BusinessException("删除字典失败.");
}
}
@Override
public void updateEffective(String type, String dictIds) {
String effective = null;
if ("1".equals(type)){
effective = "1";
}else {
effective = "0";
}
int count = sysDictWriteMapper.updateEffective(ConvertUtils.integerToList(dictIds), effective);
if (count < 1){
throw new BusinessException(type== "1" ? "改记录启用失败." : "改记录禁用失败.");
}
}
private boolean checkDictExist(SysDictVo dictVo){
return sysDictReadMapper.queryDictByTypeAndLabel(dictVo.getDictId(), dictVo.getType(), dictVo.getCode()) > 0;
}
mapper
/**
* 字典查询列表
* @param searchInfo 条件
* @return
*/
List<SysDictVo> queryDictList(@Param("searchInfo") String searchInfo);
/**
* 统计
* @param dictId id
* @param type 类型
* @param code 码值
* @return
*/
int queryDictByTypeAndLabel(@Param("dictId") Integer dictId, @Param("type") String type, @Param("code") String code);
/**
* 新增字典
* @param dictPo
* @return
*/
int addDict(SysDictPo dictPo);
/**
* 编辑字典
* @param dictPo
* @return
*/
int editDict(SysDictPo dictPo);
/**
* 删除字典
* @param dictIds 字典id列表
* @return
*/
int deleteDictByDictIds(@Param("dictIds") List<Integer> dictIds);
int updateEffective(@Param("dictIds") List<Integer> dictIds, @Param("effective") String effective);
sql
<sql id="Base_Column_List">
dictid,
type,
code,
label,
effective
</sql>
<select id="queryDictList" resultType="com.longzy.component.dict.entity.SysDictVo">
SELECT <include refid="Base_Column_List" />
FROM sys_dict
<where>
<if test="searchInfo != '' and searchInfo != null">
type = #{searchInfo,jdbcType=VARCHAR}
or
label like concat('%', #{searchInfo,jdbcType=VARCHAR}, '%')
</if>
</where>
</select>
<select id="queryDictByTypeAndLabel" resultType="int">
SELECT count(1)
FROM sys_dict
WHERE type = #{type,jdbcType=VARCHAR}
AND code = #{code,jdbcType=VARCHAR}
<if test="dictId != '' and dictId != null">
AND dictid != #{dictId,jdbcType=INTEGER}
</if>
</select>
<insert id="addDict" parameterType="com.longzy.component.dict.entity.SysDictPo">
INSERT INTO sys_dict
(`type`, code, label, effective)
VALUES(#{type,jdbcType=VARCHAR},
#{code,jdbcType=VARCHAR},
#{label,jdbcType=VARCHAR},
#{effective,jdbcType=VARCHAR})
</insert>
<update id="editDict" parameterType="com.longzy.component.dict.entity.SysDictPo">
UPDATE sys_dict
<set>
<if test="type != '' and type != null">
type = #{type,jdbcType=VARCHAR},
</if>
<if test="code != '' and code != null">
code = #{code,jdbcType=VARCHAR},
</if>
<if test="label != '' and label != null">
label = #{label,jdbcType=VARCHAR},
</if>
<if test="effective != '' and effective != null">
effective = #{effective,jdbcType=VARCHAR}
</if>
</set>
WHERE dictid = #{dictId,jdbcType=INTEGER}
</update>
<delete id="deleteDictByDictIds">
DELETE FROM sys_dict where dictid in
<foreach collection="dictIds" item="dictId" separator="," open="(" close=")">
#{dictId}
</foreach>
</delete>
<update id="updateEffective" parameterType="com.longzy.component.dict.entity.SysDictPo">
UPDATE sys_dict set effective = #{effective,jdbcType=VARCHAR}
WHERE dictid in
<foreach collection="dictIds" item="dictId" separator="," open="(" close=")">
#{dictId}
</foreach>
</update>
三、字典配置
1、获取字典缓存
在用户登录成功后将字典获取存于缓存中,防止重复调用数据库而降低效率,在router路由中获取,如:
router.beforeEach((to, from, next) => {
let dict = sessionStorage.getItem('dict')
if (token){
// 已登录
if (to.path == '/login') {
next({path: '/'})
}else{
// 字典缓存赋值
if (dict == null){
store.dispatch('queryDictCache')
}else {
store.state.dict.dict = JSON.parse(dict)
}
}
}else {
// 未登录跳转登录页
if (to.path == '/login') {
next()
} else if (!token) {
next({path: '/login'})
}
}
next()
})
2、将数据存于store中
import Vue from 'vue'
import Vuex from 'vuex'
import dictJs from '@/views/project/dict/api/index'
Vue.use(Vuex)
export default {
state: {
dict: [],
},
getters: {
// 根据类型获取码值列表
getDictList(state, getters){
return function(type){
return state.dict.filter(e => e.type == type)
}
},
// 根据类型和code获取码值列表
getDictLabelByType(state, getters){
return function(type, code){
let dictList = []
let label = ''
dictList = state.dict.filter(e => e.type == type)
if (dictList.length > 0){
dictList.forEach(e => {
if (e.code == code){
label = e.label
}
});
}
return label
}
}
},
mutations: {
// 将字典存入session会话中
SET_DICT(state, dict){
state.dict = dict
sessionStorage.setItem("dict", JSON.stringify(dict))
}
},
actions: {
queryDictCache({ commit, state }){
return new Promise((resolve, reject) => {
dictJs.queryDictCache().then(res => {
commit('SET_DICT', res.data.data)
resolve(res)
}).catch(error => {
reject(error)
})
})
}
}
}
3、字典的调用
(1)、通过类型获取字典集合
一般用户在编辑表单中使用,如:
通过调用store中getters中方法,如:$store.getters.xxx()
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="formData.menuType">
<el-radio
v-for="item in $store.getters.getDictList('MENUTYPE')"
:key="item.code"
:label="item.code">
{{item.label}}
</el-radio>
</el-radio-group>
</el-form-item>
(2)、通过类型和值获取名称
一般用户表格数据展示,如:
通过调用store中getters中方法,如:$store.getters.xxx()
<!-- 有效状态 -->
<template slot="effective" slot-scope="{scope}">
<el-tag
:type="scope.row.effective === '1' ? 'success' : 'danger'"
disable-transitions>{{$store.getters.getDictLabelByType('EFFECTIVE', scope.row.effective)}}</el-tag>
</template>
代码地址:
后端: https://gitee.com/longzyl/longzy-admin
前端: https://gitee.com/longzyl/longzy-vue2