打造一款適合本身的快速開發框架-前端篇之字典組件設計與實現

前言

後端篇的時候已經對字典模塊進行了設計,相應的接口也已經完成。在先後端未分離的狀況下,由於頁面是由服務端渲染的,因此通常都會自定義一個字典標籤用於對字典數據的取值、渲染。該種狀況下,服務端很方便地對字典作緩存處理。先後端分離後,前端與後端都是經過接口進行交互的,因此維護字典的方式也會有所區別。前端

字典組件設計

咱們簡單的分析一下字典組件應該具有的功能。vue

先分析使用場景

在後臺管理中,常見的使用字典的場景有三個:git

  1. 添加/修改表單,下拉選擇
  2. 列表頁,由值轉爲顯示名稱
  3. 搜索表單,下拉選擇

使用場景不一樣,對於前端來講,其實就是呈現方式的不一樣。因此咱們在作組件的時候,能夠先默認按場景分三種佈局。vuex

關於緩存的思路

緩存的思路有不少種,這裏簡單講一下:數據庫

  1. 系統登陸後,一次性返回全部的字典數據,緩存在本地的cookies或vuex上;json

    優勢:減輕服務器壓力後端

    缺點:一次性返回,字典量多的話,可能會影響體驗api

  2. 不進行緩存,每次都調用接口獲取數據;緩存

    優勢:無bash

    缺點:頻繁請求,頁面中字典多的話,影響體驗

  3. 使用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本地字典類型,若是本地存在字典配置,就能夠不走接口請求。目前該參數也未實現。

項目源碼地址

  • 後端

gitee.com/mldong/mldo…

  • 前端

gitee.com/mldong/mldo…

相關文章

打造一款適合本身的快速開發框架-先導篇

打造一款適合本身的快速開發框架-前端腳手架搭建

打造一款適合本身的快速開發框架-前端篇之登陸與路由模塊化

打造一款適合本身的快速開發框架-前端篇之框架分層及CURD樣例

相關文章
相關標籤/搜索