background
Years later, in a meaty booth, Tao will remember the bland but unapologetic tone I used when I introduced him to the scheme. Business development partners know that the management of data dictionary is extremely important in any slightly large-scale front-end project, but there is no coherent idea how to do it well. A beginner front-end programmer would probably write code like this:
</option> <option value="2"> </option> <option value="3"> </option> </select> {filters: {role(value) {switch (value) {case 1: return 'Liu Bei' case 2: return 'Guan Gong' case 3: return 'Zhang Fei'}}}}Copy the code
And then in other parts of the system, just copy it. Gradually, the system will be filled with random copy and paste of homogeneous data, once the need to add, delete and change, will inevitably affect all relevant places, otherwise there will be a variety of inconsistent data problems. What are the causes of these problems and how can they be solved? The answers are as follows:
- The reason of the problem is that the developers do not analyze the architecture and evolution of the system with data structure as the center, but only meet the requirements of the current module.
- To solve such problems, it is necessary to follow the principle of unified data management, especially for homogeneous data, single data source must be guaranteed as far as possible, and all other display forms, such as selectors and filters, must be traced to a single data source.
Based on this, a complete design scheme will be presented in the next section. The whole scheme will be implemented based on Vue 2.x framework.
design
Our design follows the principle of a single data source. Data sources are called data dictionaries. Data dictionaries can be divided into two types according to their sources: static data dictionaries maintained by the front end and dynamic data dictionaries maintained by the back end and accessed by interfaces; There are two types of interface display and interaction modes: filter and selector.
Mind mapping
The data dictionary
A data dictionary is a single data source with directories assigned as SRC/Model /dicts. The data dictionary contains two types, one is the key/value negotiated by the front end and the other is the dynamic maintenance provided by the back end to the front end of the API interface. Static dictionaries are named static-busline prefixes.js, and dynamic dictionaries are named ** dynamic-busline prefixes.js. Dictionary key values are divided by line of business prefix and specific description, detailed data format in the implementation section.
The filter
Filters are global and common to the whole system. Because dictionary key values are already namespace isolated by line of business prefix, there is no possibility of conflicts. Registration is provided as a Vue plug-in.
A component reference
The scenarios referenced by the component include: search forms, filters, and entry forms. The search form and the entry form can be packaged as separate components, and the filter simply takes dictionary keys.
implementation
Business line to the Romance of The Three Kingdoms as an example, prefix: sgyy.
The data dictionary
Static dictionary
Create a new SRC /model/dicts directory. For static dictionaries, create static-sgyy.js under this directory
// @notice Is prefixed with the abbreviation of the corresponding service line to avoid service conflicts
export default {
dictArr: {
sgyyWei: [{key: '1'.value: "Cao cao }
{ key: '2'.value: Sima Yi}].sgyyShu: [{key: '1'.value: 'liu bei' },
{ key: '2'.value: 'the duke guan },
{ key: '3'.value: 'zhang fei'}].sgyyWu: [{key: '1'.value: 'sun' },
{ key: '2'.value: 'zhou yu'}}}]Copy the code
Create a new index.js file under the directory as the dictionary integration export layer
let subDictKeys = [
'./static-sgyy',]export default {
dictArr: {
common: [{key: '1'.value: 'Shared dictionary fields'}},// Provide the function of converting to a key-value pair externally
getDict: function (key) {
var arr = this.dictArr[key]
var dict = {}
for (var i = 0; i < arr.length; i++) {
dict[arr[i].key + ' '] = arr[i].value
}
return dict
},
// Use the original array
getDictArr: function (key) {
return this.dictArr[key]
}
}
subDictKeys.forEach(item= > {
let subDicts = require(item+' ')
dicts.dictArr = mix(dicts.dictArr, subDicts.dictArr)
})
function mix(o, n) {
var obj = o || {}
for (var p in n) {
if(n.hasOwnProperty(p) && (! o.hasOwnProperty(p))) { o[p] = n[p] } }return obj
}
Copy the code
Dynamic dictionary
In the SRC /model/dicts directory, create the dynamic-sgyy.js file
export default {
sgyyAsyncWeiHero: {
url: constant.baseUri + '/heros/wei'.optionKey: { // Specify which two fields should be used as keys/values for the data returned by the back end. This is usually negotiated with the back end developer
label: 'name'.value: 'code'}}}Copy the code
The filter
Filter implementation is only for static data dictionary, dynamic dictionary can not be made into a filter, API interface needs to return the corresponding description, the front end directly display description dictionary. The filter implementation is as follows
import dicts from '@/model/dicts/index'
export default {
install(Vue) {
var dictArr = dicts.dictArr
for (var key in dictArr) {
if (dictArr.hasOwnProperty(key)) {
(function (key) {
Vue.filter(key, function (n) {
var item = dicts.getDict(key)
return item[n + ' '] | |The '-'
})
}(key))
}
}
}
}
Copy the code
A component reference
Static dictionary component
We encapsulate the selector corresponding to the static dictionary as a generic component and register it as a global component. The following code
<template> <el-select :placeholder="calcPlaceholder" clearable :value="value + ''" @change="changeFn" > <el-option v-for="item in dictArr" :value="item.key" :key="item.key" :label="item.value" /> </el-select> </template> <script> const PLC_MAP = {SEARCH: 'all ', DATAOPR:' select '} export default {name: 'StaticDictSelect', model: {prop: 'value', event: 'change' }, props: { value: { type: String | Number, default: '' }, dictArr: { type: Array, default: [] }, actionType: {/ / 'SEARCH' = > SEARCH operation | 'DATAOPR' = > data operation type: 'the SEARCH' | 'DATAOPR, default:' DATAOPR}, placeholder: '' }, computed: { calcPlaceholder() { return this.placeholder || PLC_MAP[this.actionType] } }, data() { return { PLC_MAP: { SEARCH: 'all ', SELECT' please SELECT '}}}, methods: { changeFn(value) { this.$emit('change', value) } } } </script> <style lang="scss" scoped></style>Copy the code
The component is invoked as follows
<template> < GP-page class="page"> < GP-search > <el-form inline :model="table.params" > <el-form-item label=" payment status "> <gp-static-dict-select actionType="SEARCH" v-model="table.params.wei" :dictArr="searchDicts.wei" /> </el-form-item> </el-form> <div slot="right"> <el-button type="primary" @click="search(1)"> query </el-button> <el-button </el-button> </div> </ gP-search > < GP-table :tableData="table" :loading="table.loading" @change="search" > <el-table-column label=" name" prop="name" min-width="200" show-overflow-tooltip > <template slot-scope="scope"> {{ scope.row.wei | sgyyWei }} </template> </el-table-column> </gp-table> </gp-page> </template> <script> import dicts from '@M/dicts/index' const INIT_SEARCH_FILTER = { wei: '', } export default { name: 'page', data() { return { searchDicts: { wei: dicts.getDictArr('sgyyWei'), }, table: { params: { ... INIT_SEARCH_FILTER }, loading: true, total: 0, page: 1, size: 10, list: [] }, viewOrderId: '' } }, filters: {}, methods: {async search(page) {// get list data const res = await this.$post('/search', {... This.table.params}) /* /}, resetSearchFilter() {this.table.params = {... INIT_SEARCH_FILTER } } }, mounted() { this.search() } } </script> <style lang="scss" scoped></style>Copy the code
Dynamic dictionary component
A dynamic dictionary component can optionally be encapsulated as a generic component and registered as a global component, as shown below
< the template > < el - select class = "dynamic - dict - select" : placeholder = "calcPlaceholder" : no - data - text = "noDataText | | 'to'" :size="actionType === 'SEARCH' ? 'small' : ''" v-model="innerValue" :filterable="filterable" :clearable="clearable" :disabled="disabled" :loading="loading" :remote="remote" :remote-method="remoteMethod" @change="change" @visible-change="visibleChange" > <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </template> <script> const PLC_MAP = {SEARCH: 'all ', DATAOPR:' please select '} export default {name: 'DynamicDictSelect', props: {actionType: {/ / 'SEARCH' = > SEARCH operation | 'DATAOPR' = > data operation type: 'the SEARCH' | 'DATAOPR, default:' DATAOPR}, bindSelItem: / / {}, {url: AppendParam: {label: 'labelKey', value: 'labelKey'}} appendParam: {label: 'labelKey', value: 'labelKey'}} Type: Number, default: 0}, value: {}, label: {}, // Get label filterable: {type: Boolean, default: true }, clearable: { type: Boolean, default: true }, disabled: false, load: { type: Boolean, default: false }, placeholder: { type: String, default: '' }, remote: { type: Boolean, default: true }, paging: { type: Boolean, default: true } }, data() { return { loading: false, innerValue: '', options: [], noDataText: '', chooseArr: [] } }, watch: { appendParam: { handler(newAppendParam, oldAppendParam) { this.findData('', true) }, immediate: true, deep: true } }, computed: { calcPlaceholder() { return this.placeholder || PLC_MAP[this.actionType] } }, created() { this.remoteMethod = this.$utils.debounce(this.remoteMethod, 300) if (this.value) { this.innerValue = JSON.parse(JSON.stringify(this.value)) } this.findData() }, methods: { visibleChange(visible) { if (visible) { this.findData() } }, async findData(keywords, forceUpdate) { let pageIndex = 1 let pageSize = 50 const options = this.options if (options.length > 0 && ! forceUpdate) { return } this.chooseArr = [] let isRemote = this.remote if (this.loading) { return } if (! isRemote && this.options.length > 0) { return } this.loading = true let params = { keywords: keywords || '', name: Keywords | | '} / / processing additional parameters let appendParam = this. For appendParam (let key in appendParam) {if (appendParam.hasOwnProperty(key)) { params[key] = appendParam[key] } } params.validFlagNew = this.validFlagNew if (this.paging) { params.pageNum = pageIndex params.pageSize = pageSize params.rows = pageSize } let bindSelItem = Let cache = "if (this.innerValue) {cache = JSON.parse(JSON.stringify(this.innerValue)) this.innerValue = '' } const result = await this.$post(bindSelItem.url, { data: params }) if (result.err) { this.noDataText = result.errmsg } else { const data = result.data || {} const arr = [] let list = [] if (data.list) { list = data.list } else { list = data } list = list.splice(0, pageSize) list.forEach(item => { this.chooseArr.push(item) arr.push({ label: item[bindSelItem.optionKey['label']], value: item[bindSelItem.optionKey['value']] }) }) this.options = arr setTimeout(() => { this.$emit('load-data', Json.parse (json.stringify (arr)))}, 10) // Set the delay to solve the problem when the list is not loaded and the drop-down list is displayed in the case of a value. SetTimeout (() => {if (cache) {this.innerValue = cache}}, 25) this.noDatatext = ''}}, change(e) { this.$emit('input', e) this.getLabel(e) this.$emit('change', e) }, getLabel(value) { let label = '' let obj = {} for (let i = 0; i < this.options.length; i++) { const option = this.options[i] if (option.value == value) { label = option.label obj = this.chooseArr[i] break } } this.$emit('update:label', label) this.$emit('listenToChildListLabel', label) this.$emit('listenToChildListEvent', obj) }, remoteMethod(query) { this.findData(query, true) } }, watch: { value(value) { this.innerValue = value this.getLabel(value) } } } </script> <style lang="scss"> .gp-dynamic-dict-select { .el-select__caret { &.el-input__icon:not(.el-icon-circle-close) { &:before { content: '\E6E1'; } } } } </style>Copy the code
An example of how a dynamic dictionary component is referenced is as follows
<template> <div class="page"> <layout-search> <el-form ref="sgyySearchForm" inline label-width="100px" < gP-dynamic-dict-select actionType="SEARCH" :model="searchForm" > <el-form-item label=" weiHero"> v-model="searchForm.weiHero" :bindSelItem="bindDynamicSel.sgyyAsyncWeiHero" /> </el-form-item> </el-form> <div Slot ="right"> <el-button type="primary" @click="search(1)"> query </el-button> </div> </layout-search> <el-dialog :visible="visble"> <el-form ref="sgyyDataForm" inline label-width="100px" :model="dataForm"> <el-form-item label=" prop="weiHero"> <gp-dynamic-dict-select actionType="DATAOPR" v-model="dataForm.weiHero" :bindSelItem="bindDynamicSel.sgyyAsyncWeiHero" /> </el-form-item> </el-form> </el-dialog> </div> </template> <script> import bindDynamicSel from "@M/dicts/dynamic-sgyy"; export default { data() { return { searchForm: { weiHero: "", }, bindDynamicSel: bindDynamicSel, visble: false, dataForm: { weiHero: "", }, }; }, methods: { async search(page) { const searchPage = page || 1; const result = await this.$post(constant.baseURI + "/search/heros", { data: { ... This. SearchForm, pageNum: searchPage, pageSize: 20,}}) </script> <style lang="scss" scoped></style>Copy the code
To optimize the
The current design of the scheme is static dictionary and dynamic dictionary independent maintenance, independent use, is it possible to merge the two sets of static and dynamic dictionary into one? Secondly, in the current scheme, the general component requires the caller to pass in many attributes. For example, the static dictionary component requires the caller to inject the dictionary array, while the dynamic dictionary component needs to specify bindSelItem. Is it possible to make another layer of encapsulation, and the caller only needs to specify the key value? Of course you can!
conclusion
This data dictionary integrated management scheme realizes unified and intensive management of system dictionary data, and makes all interface forms including selector and filter only depend on a single data source. Ultimately, the underlying data layer remains stable no matter how the system interface or interaction pattern changes. Product iterative evolution, only need to change a single data source, easy to say goodbye to global search and replacement. Finally, as usual, attached source: github.com/sq580kzfed/… .