数据驱动,快速开发组件(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, 搜索栏已经所有作成了可复用的组件,只须要配置好相关数据,引入组件便可使用。 关于组件的相关逻辑,可能要在文章里面一次性说清楚,仍是须要费很大的精力,不过但愿数据驱动的思想可以让以前没有体会到这种开发乐趣的小伙伴们有到新的想法。
相关文章
相关标签/搜索