數據驅動,快速開發組件(ElementUI篇)

在平常開發中,咱們確定不止一次碰到重複的業務代碼,明明功能類似,但總沒思路去把它封裝成組件。關於封裝組件,但願這篇文章能帶給你們新的思路,去更高效的完成平常開發。(注:例子都是基於ElementUI, 但思路都是同樣的)

示例地址-> https://www.lyh.red/adminnode

代碼地址git

數據驅動

  • 構建頁面:設計數據結構(綁定value,綁定事件,相關屬性)-> 生成dom -> dom綁定相關
  • 監聽事件:操做UI -> 觸發事件 -> 更新數據 -> 更新UI

數據驅動是基於數據觸發的,在編寫業務的時候,只須要編寫好組件的dom結構,以後咱們即可以不用再去關心dom層,只須要關心數據就ok。
基於這種思路,那留給咱們的只有兩步,組件設計和數據設計。github

先看看效果

搜索欄配置以及生成效果
// 過濾相關配置
      filterInfo: {
        query: {
          create_user: '',
          account: '',
          name: ''
        },
        list: [
          {type: 'input', label: '帳戶', value: 'account'},
          {type: 'input', label: '用戶名', value: 'name'},
          // {type: 'select', label: '建立人', value: 'create_user'},
          // {type: 'date', label: '建立時間', value: 'create_time'},
          {type: 'button', label: '搜索', btType: 'primary', icon: 'el-icon-search', event: 'search', show: true},
          {type: 'button', label: '添加', btType: 'primary', icon: 'el-icon-plus', event: 'add', show: true}
        ]
      }

clipboard.png

表格配置以及生成效果
// 表格相關
      tableInfo: {
        refresh: false,
        initCurpage: false,
        data: [],
        fieldList: [
          {label: '帳號', value: 'account'},
          {label: '用戶名', value: 'name'},
          {label: '性別', value: 'sex', width: 80, list: 'sexList'},
          {label: '帳號類型', value: 'type', width: 100, list: 'accountTypeList'},
          {label: '狀態', value: 'status', width: 90, list: 'statusList'},
          {label: '建立人', value: 'create_user'},
          {label: '建立時間', value: 'create_time', minWidth: 180},
          {label: '更新人', value: 'update_user'},
          {label: '更新時間', value: 'update_time', minWidth: 180}
        ],
        handle: {
          fixed: 'right',
          label: '操做',
          width: '180',
          btList: [
            {label: '編輯', type: '', icon: 'el-icon-edit', event: 'update', show: true},
            {label: '刪除', type: 'danger', icon: 'el-icon-delete', event: 'delete', show: true}
          ]
        }
      }

clipboard.png

dom配置和完整頁面
<template>
  <div class="app-container">
    <!-- 條件欄 -->
    <page-filter
      :query.sync="filterInfo.query"
      :filterList="filterInfo.list"
      :listTypeInfo="listTypeInfo"
      @handleClickBt="handleClickBt"
      @handleEvent="handleEvent">
    </page-filter>
    <!-- 表格 -->
    <page-table
      :refresh="tableInfo.refresh"
        :initCurpage="tableInfo.initCurpage"
        :data.sync="tableInfo.data"
        :api="getListApi"
        :query="filterInfo.query"
        :fieldList="tableInfo.fieldList"
        :listTypeInfo="listTypeInfo"
        :handle="tableInfo.handle"
        @handleClickBt="handleClickBt"
        @handleEvent="handleEvent">
    </page-table>
    <!-- 彈窗 -->
    <page-dialog
      :title="dialogInfo.title[dialogInfo.type]"
      :visible.sync="dialogInfo.visible"
      :width="dialogInfo.width"
      :btLoading="dialogInfo.btLoading"
      :btList="dialogInfo.btList"
      @handleClickBt="handleClickBt"
      @handleEvent="handleEvent">
      <!-- form -->
      <page-form
      :refObj.sync="formInfo.ref"
      :data="formInfo.data"
      :fieldList="formInfo.fieldList"
      :rules="formInfo.rules"
      :labelWidth="formInfo.labelWidth"
      :listTypeInfo="listTypeInfo">
      </page-form>
    </page-dialog>
  </div>
</template>

clipboard.png

封裝一個搜索欄(功能欄)組件

根據需求設計數據結構

參數設計

搜索參數query,好比要查詢的參數有帳號,名字。api


dom相關屬性設計

首先要考慮dom的類型,和顯示,這是基本的,還有擴展類型,好比事件能夠設置event屬性,是否顯示設置show屬性,這些是比較通用的。
而基於不一樣類型的dom,若是是input,select,datetime類型的dom,做爲一個承載數據的容器,則須要一個value屬性去和query中的屬性名對上,除此以外不一樣類型的dom還有不一樣的特定屬性,好比select須要提供對應的list,datetime須要相關的pickersOptions去限制時間範圍,若是是按鈕,好比el-button,則能夠設置icon,按鈕相關type。數據結構

最終實現:app

filterInfo: {
        query: {
          create_user: '',
          account: '',
          name: ''
        },
        list: [
          {type: 'input', label: '帳戶', value: 'account'},
          {type: 'input', label: '用戶名', value: 'name'},
          // {type: 'select', label: '建立人', value: 'create_user'},
          // {type: 'date', label: '建立時間', value: 'create_time'},
          {type: 'button', label: '搜索', btType: 'primary', icon: 'el-icon-search', event: 'search', show: true},
          {type: 'button', label: '添加', btType: 'primary', icon: 'el-icon-plus', event: 'add', show: true}
        ]
      }

循環的dom列表dom

設計dom結構

先考慮設計的這個dom須要什麼屬性

好比dom是el-input,一個輸入框,能夠設置是否禁止disabled,能夠設置是否可清空clearable,v-model要綁定的數據,設置dom的class名,設置dom綁定的事件。
好比dom是el-select, 除了上面這些屬性,咱們還須要這個元素可循環的list函數

最終dom結構爲:ui

<div class="filter-item" v-for="(item, index) in getConfigList()" :key="index">
      <!-- <label class="filter-label" v-if="item.type !== 'button'">{{item.key}}</label> -->
      <!-- 輸入框 -->
      <el-input
        :class="`filter-${item.type}`"
        v-if="item.type === 'input'"
        :type="item.type"
        :disabled="item.disabled"
        :clearable="item.clearable || true"
        :placeholder="getPlaceholder(item)"
        @focus="handleEvent(item.event)"
        v-model="searchQuery[item.value]">
      </el-input>
      <!-- 選擇框 -->
      <el-select
        :class="`filter-${item.type}`"
        v-if="item.type === 'select'"
        v-model="searchQuery[item.value]"
        :disabled="item.disabled"
        @change="handleEvent(item.even)"
        :clearable="item.clearable || true"
        :filterable="item.filterable || true"
        :placeholder="getPlaceholder(item)">
        <el-option v-for="(item ,index) in  listTypeInfo[item.list]" :key="index" :label="item.key" :value="item.value"></el-option>
      </el-select>
      <!-- 時間選擇框 -->
      <el-time-select
        :class="`filter-${item.type}`"
        v-if="item.type === 'time'"
        v-model="searchQuery[item.value]"
        :picker-options="item.TimePickerOptions"
        :clearable="item.clearable || true"
        :disabled="item.disabled"
        :placeholder="getPlaceholder(item)">
      </el-time-select>
      <!-- 日期選擇框 -->
      <el-date-picker
        :class="`filter-${item.type}`"
        v-if="item.type === 'date'"
        v-model="searchQuery[item.value]"
        :picker-options="item.datePickerOptions || datePickerOptions"
        :type="item.dateType"
        :clearable="item.clearable || true"
        :disabled="item.disabled"
        @focus="handleEvent(item.event)"
        :placeholder="getPlaceholder(item)">
      </el-date-picker>
      <!-- 按鈕 -->
      <el-button
        :class="`filter-${item.type}`"
        v-else-if="item.type === 'button'"
        v-waves
        :type="item.btType"
        :icon="item.icon"
        @click="handleClickBt(item.event)">{{item.label}}</el-button>
    </div>
  </div>

事件的處理

事件怎麼綁定在dom上

綁定事件,能夠在數據結構中給dom設置一個event屬性,好比說是查詢search,在dom結構中咱們能夠設計一箇中間層函數去處理,好比:this

<!-- 按鈕 -->
      <el-button
        :class="`filter-${item.type}`"
        v-else-if="item.type === 'button'"
        v-waves
        :type="item.btType"
        :icon="item.icon"
        @click="handleClickBt(item.event)">{{item.label}}</el-button>

中間層函數接收事件類型,而後統一處理。

組件中的函數,外部怎麼處理

我以爲組件的話,就承載一個去重複的做用,將因此重複的事情去除就能夠,像若是是表格,表單,功能欄相似這種可能顯示重複可是事件多變性的組件,咱們則能夠考慮將它們的事件派發到業務相關頁面處理,組件保持去除重複的工做,簡單幹淨明瞭就行了。
將事件所有交給父級處理:

// 綁定的相關事件
    handleEvent (evnet) {
      this.$emit('handleEvent', evnet)
    },
    // 派發按鈕點擊事件
    handleClickBt (event, data) {
      this.$emit('handleClickBt', event, data)
    }

封裝一個tree組件

在後臺管理頁面樹狀組件用到次數實在太多了,靜態的樹數據加載,動態的樹數據懶加載,左鍵點擊事件,右鍵點擊事件等等,封裝以後,哼哼,誰用誰知道,一個字,爽。

設計屬性

其實就是將elementui中的大部分用上的tree屬性加上,而後再設計一部分讓組件更加好用的屬性,部分舉個例子。
屬性 類型 描述
lazy Boolean 是否懶加載
lazyInfo Array 懶加載相關數據
loadInfo Object 正常相關數據
rightClick Boolean 是否須要右鍵菜單
rightMenuList Array 右鍵菜單列表
懶加載數據和正常加載數據結構的詳細設計
/**
     * 懶加載相關數據
     * key -> 惟一標識 label -> 顯示 type -> 類型 api -> 接口 params -> 參數 leaf -> 是否葉子節點
     */
    lazyInfo: {
      type: Array,
      default: () => {
        return [
          {
            key: 'id',
            label: 'name',
            type: '',
            api: () => {},
            params: {key: '', value: '', type: 'url'}, // url/query->{data: [{key: '', value: '', default: ''}] type: 'query'}
            leaf: true
          }
        ]
      }
    },
    /**
     * 正常加載相關
     */
    loadInfo: {
      key: 'id',
      label: 'name',
      api: () => {},
      params: {key: '', value: '', type: 'url'} // url/query->{data: [{key: '', value: '', default: ''}] type: 'query'}
    },

事件處理

事件處理一樣是須要派發到父級處理,以保證組件的可複用性,經過中間件將樹組件的相關事件派發搭到父級。

實現效果

懶加載樹組件相關數據配置:

// 樹相關信息
      treeInfo: {
        refresh: false,
        refreshLevel: 0,
        nodeKey: 'key',
        lazy: true,
        type: 0, // 省市區類型
        lazyInfo: [
          {
            key: 'id',
            label: 'name',
            type: 1,
            api: getAllApi,
            params: {key: 'pid', value: 1, type: 'url'}
          },
          {
            key: 'id',
            label: 'name',
            type: 2,
            api: getAllApi,
            params: {key: 'pid', value: '', type: 'url'},
            leaf: true
          }
        ],
        rightMenuList: []
      },

懶加載樹dom結構:

<div class="page-tree" v-loading="treeLoading" @contextmenu.prevent="handleTreeClick">
    <el-tree
      class="tree-component disabled-select"
      ref="TreeComponent"
      :show-checkbox="checkBox"
      :node-key="nodeKey"
      :data="treeData"
      :load="handleLoadNode"
      :lazy="lazy"
      :draggable="draggable"
      :allow-drop="handleDrop"
      :expand-on-click-node="false"
      :check-strictly="checkStrictly"
      :filter-node-method="filterNode"
      :default-checked-keys="defaultChecked"
      :default-expanded-keys="defaultExpanded"
      @node-click="handleClickLeft"
      @node-contextmenu="handleClickRight"
      @check="handleCheck"
      @check-change="handleCheck"
      @current-change="handleCheck"
      @node-expand="handleCheck"
      highlight-current
      :render-content="renderContent"
      :props="treeProps">
    </el-tree>
    <!-- 右鍵菜單 -->
    <ul class='contextmenu' v-show="rightMenu.show" :style="{left: rightMenu.left +'px',top: rightMenu.top +'px'}">
      <li v-for="(item, index) in rightMenu.list" :key="index" @click="handleRightEvent(item.type, item.data, item.node, item.vm)">{{item.name}}</li>
    </ul>
  </div>

實現效果:

clipboard.png

總結

本文之後臺管理頁面爲例,通常後臺管理頁面經常使用到的tree, table, form, dialog, 搜索欄已經所有作成了可複用的組件,只須要配置好相關數據,引入組件便可使用。 關於組件的相關邏輯,可能要在文章裏面一次性說清楚,仍是須要費很大的精力,不過但願數據驅動的思想可以讓以前沒有體會到這種開發樂趣的小夥伴們有到新的想法。
相關文章
相關標籤/搜索