后端篇的时候已经对字典模块进行了设计,相应的接口也已经完成。在先后端未分离的状况下,由于页面是由服务端渲染的,因此通常都会自定义一个字典标签用于对字典数据的取值、渲染。该种状况下,服务端很方便地对字典作缓存处理。先后端分离后,前端与后端都是经过接口进行交互的,因此维护字典的方式也会有所区别。前端
咱们简单的分析一下字典组件应该具有的功能。vue
在后台管理中,常见的使用字典的场景有三个:git
使用场景不一样,对于前端来讲,其实就是呈现方式的不一样。因此咱们在作组件的时候,能够先默认按场景分三种布局。vuex
缓存的思路有不少种,这里简单讲一下:数据库
系统登陆后,一次性返回全部的字典数据,缓存在本地的cookies或vuex上;json
优势:减轻服务器压力后端
缺点:一次性返回,字典量多的话,可能会影响体验api
不进行缓存,每次都调用接口获取数据;缓存
优势:无bash
缺点:频繁请求,页面中字典多的话,影响体验
使用vuex,基于dictKey进行缓存,保证在同一个vue实例下,同一个key,只调用一次接口。
方案三是本框架采用的方式,也不能说是最优的。可是相对而已,可能会比前两个方案会好一些。固然,除了这三个方案,确定还有别的方案,这里就不讨论了。
暂时定几个经常使用的参数,后续可能还会有追加
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
dictKey | String | undefined | 字典惟一编码(表名_字段名) |
type | String | enum | 字典类型(enum->枚举类字典类型,db->数据库字典类型,local->本地字典类型) |
value | String, Number | undefined | 绑定的值 |
size | String | medium | 对应el-select的size,medium/small/mini |
mode | String | form | form->普通表单,list->列表页,searchForm->搜索表单 |
先简单说一下后端提供的接口
请求地址:
{{api_base_url}}/sys/dict/getByDictKey
数据类型:
application/json
请求示例:
{
"dictKey": "sys_role_role_type",
"type": "enum"
}
复制代码
响应示例:
{
"code": 0, // 返回状态码0,成功
"msg": "经过字典惟一编码查询成功", // 消息描述
"data": {
"name": "角色类型",
"dictKey": "sys_role_role_type", // 字典惟一编码
"items": [{
"name": "管理员",
"dictItemValue": 10
}, {
"name": "流程审核员",
"dictItemValue": 20
}]
}
}
复制代码
├── src
├── components/m
├── Dict
└── index.vue
├── store
├── modules
└── dict.js
├── getters.js
└── index.js
├── views
├── dashboard
└── index.vue
└── main.js
复制代码
src/components/m/Dict/index.vue
字典组件
<template>
<div class="m-dict">
<!--表单布局模式-->
<slot v-if="mode==='form'" v-bind:dict="dict">
<el-select :size="size" v-model="mValue" v-if="dict.items" @change="handleChange">
<el-option
v-for="item in dict.items"
:key="item.dictItemValue"
:label="item.name"
:value="item.dictItemValue">
</el-option>
</el-select>
</slot>
<!--列表布局模式-->
<slot v-else-if="mode==='list'" v-bind:dict="dict">
<span v-for="item in dict.items" :key="item.dictItemValue">
<el-tag :type="type" size="mini" v-if="item.dictItemValue === value">{{ item.name }}</el-tag>
</span>
</slot>
<!--搜索表单布局模式-->
<slot v-else-if="mode==='searchForm'" v-bind:dict="dict">
<el-select :size="size" v-model="mValue" v-if="dict.items" @change="handleChange">
<el-option label="全部" :value="undefined"></el-option>
<el-option
v-for="item in dict.items"
:key="item.dictItemValue"
:label="item.name"
:value="item.dictItemValue">
</el-option>
</el-select>
</slot>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'MDict',
props: {
// 字典惟一编码(表名_字段名)
dictKey: {
type: String,
default: undefined
},
// 字典类型(enum->枚举类字典类型,db->数据库字典类型,local->本地字典类型)
// 不传的话,后端先查enum,再查db
type: {
type: String,
default: 'enum'
},
// 绑定的值
value: {
type: [String, Number],
default: undefined
},
size: { // medium/small/mini
type: String,
default: 'medium'
},
mode: { // form->普通表单,list->列表页,searchForm->搜索表单
type: String,
default: 'form'
}
},
data() {
return {
mValue: this.value
}
},
computed: {
...mapGetters([
'dictMap'
]),
// 当前字典
dict() {
return this.dictMap[this.dictKey] || {}
}
},
watch: {
value(n) { // 监听父组件值变更,子组件也要变更
this.mValue = n
}
},
created() {
if (!this.dictMap[this.dictKey]) {
// 这里调用store/modules/dict.js/action->getByDictKey
this.$store.dispatch('dict/getByDictKey', {
dictKey: this.dictKey,
type: this.type
})
}
},
methods: {
// 子组件值变化要通知父组件
handleChange(value) {
this.$emit('input', value)
}
}
}
</script>
复制代码
src/store/modules/dict.js
import request from '@/utils/request'
const getDefaultState = () => {
return {
// 字典map
dictMap: {}
}
}
const state = getDefaultState()
const mutations = {
// 保存字典项
SAVE_DICT_ITEM: (state, data) => {
var obj = {}
obj[data.dictKey] = data
// 须要拷贝一份,要否则数据变更监听不到
state.dictMap = Object.assign({}, state.dictMap, obj)
},
// 移除字典项
DELETE_DICT_ITEM: (state, dictKey) => {
delete state.dictMap[dictKey]
}
}
const actions = {
// 获取字典的action
getByDictKey({ commit }, data) {
return new Promise((resolve, reject) => {
if (state.dictMap[data.dictKey]) {
resolve()
} else {
// 防止同一个key屡次请求
commit('SAVE_DICT_ITEM', {
dictKey: data.dictKey,
items: []
})
// 这里暂不用api.service.js
request({
url: '/sys/dict/getByDictKey',
method: 'post',
data
}).then(res => {
if (res.code === 0 && res.data) {
commit('SAVE_DICT_ITEM', res.data)
} else {
commit('DELETE_DICT_ITEM', data.dictKey)
}
resolve()
}).catch(error => {
commit('DELETE_DICT_ITEM', data.dictKey)
reject(error)
})
}
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
复制代码
src/store/getters.js
定义dictMap的get方法
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
// 这里追加dictMap的get方法,可使用mapGetters,详见src/components/m/Dict/index.vue
dictMap: state => state.dict.dictMap
}
export default getters
复制代码
src/store/index.js
这里引入dict.js模块,充分利用了require.contex
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
// import app from './modules/app'
// import settings from './modules/settings'
// import user from './modules/user'
// 自动注册vuex模块
const files = require.context('./modules', true, /\.js$/)
var modules = {}
files.keys().forEach((routerPath) => {
const name = routerPath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = files(routerPath)
const fileModule = value.default
modules[name] = fileModule
}, {})
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
...modules
},
getters
})
export default store
复制代码
src/main.js
主入口全局注册自定义组件,这里也用了require.context,代码片断
import Vue from 'vue'
// 处理自定义组件全局注册
const files = require.context('./components/m', true, /\.vue$/)
files.keys().forEach((routerPath) => {
const componentName = routerPath.replace(/^\.\/(.*)\/index\.\w+$/, '$1')
const value = files(routerPath)
Vue.component('m-' + componentName.toLowerCase(), value.default)
}, {})
复制代码
src/views/dashboard/index.vue
这里提供了使用样例:
自定义布局
<m-dict v-model="form.roleType" dict-key="sys_role_role_type">
<template v-slot:default="{ dict }">
<el-select v-model="form.roleType" v-if="dict.items">
<el-option
v-for="item in dict.items"
:key="item.dictItemValue"
:label="item.name"
:value="item.dictItemValue">
</el-option>
</el-select>
</template>
</m-dict>
复制代码
表单布局模式
<m-dict mode="form" v-model="form.roleType" dict-key="sys_role_role_type"></m-dict>
复制代码
列表布局模式
<m-dict mode="list" v-model="form.roleType" dict-key="sys_role_role_type"></m-dict>
复制代码
搜索表单布局模式
<m-dict mode="searchForm" v-model="form.roleType" dict-key="sys_role_role_type"></m-dict>
复制代码
本文对字典组件的封装仍是比较粗糙,不过也基本上知足日常使用,如后续场景须要,再考虑继续扩展。好比预留的local本地字典类型,若是本地存在字典配置,就能够不走接口请求。目前该参数也未实现。