Vue踩坑之制作自己的组件工具

Vue相信玩前端的小伙伴一定不会陌生。使用Vue最大的好处就是组件化,通过组件的复用可以大大减小我们日常开发的工作量。最近在工作中遇到这样的情况,于是便趁着周末正好将自己写的组件整理一下,做成更加通用的组件。

先介绍一下背景。这段时间因为工作上的需要,要做后台管理系统,这类系统相信很多小伙伴也很熟悉。主要以表单的填写以及表格的展示、编辑为主。前端这一部分采用Vue全家桶,UI组件采用的是iview。UI组件大大提高了开发效率以及降低了开发的复杂度,但是组件也不是万能的,在面对实际的业务时,还是需要靠我们自己来制作组件。而这一次要制作的组件就是可以根据传入的数据不同,自动生成不同样式的表单以及表格了。(当然,利用的组件还是基于iview的)

制作思路

组件的本质就是拼装。通过拼装一个个原子组件,形成一个大组件最后来满足我们的实际业务需求。而在我这一次的需求中,原子组件已经有iview提供了。我要做的就是怎样来制作出想要的表单以及表格了。Vue提倡的思想就是数据驱动,所以在与其说是拼装组件,更不如说是设计一个合理的数据结构。

首先是表单,通过v-for对数据进行循环,然后判断数据对应的表单组件类型就可以了。因此我们需要的数据结构就需要组件的类型、绑定的值、值对应的变量名(类似username:'用户'),而其他的属性就可以根据需要再做追加。

再是表格,同样也是通过v-for对数据进行循环展示。在编辑时也需要切换到对应的组件。因此与表单的数据结构设计相同,我们需要组件的类型、绑定的值、值对应的变量名(类似username:'用户')。

而其中的难点,无非就是在于行数和列数的计算上了。怎么灵活地变换行数,还是需要利用到v-for循环中的index来进行帮忙。相信这些,只要看过一眼代码就马上可以理解。

// commonForm 代码
<template>
  <div class="wrapper">
    <Form ref="commonForm" :label-width="100">
      <Row v-for="(rowNum, rowIndex) in _formRow" :key="rowIndex" :gutter="10">
        <Col v-for="(colNum, colIndex) in _colNum" :span="_formSpan" :key="colIndex">
        <FormItem v-if="(rowNum - 1) * _colNum + colIndex < _formLength" :label="_formLayout[(rowNum - 1) * _colNum + colIndex].title">
          <!-- 输入框 input -->
          <Input v-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'text'" type="text" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Input>
          <!-- 输入框 textarea -->
          <Input v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'textarea'" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" type="textarea" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Input>
          <!-- 级联菜单 -->
          <Cascader v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'cascader'" :data="_formLayout[(rowNum - 1) * _colNum + colIndex].data" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder"></Cascader>
          <!-- 下拉菜单 -->
          <Select v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type === 'selection'" v-model="_formLayout[(rowNum - 1) * _colNum + colIndex].value" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder">
              <Option v-for="(opt, optIndex) in _formLayout[(rowNum - 1) * _colNum + colIndex].selection" :key="optIndex" :value="opt.value">{{opt.title}}</Option>
            </Select>
          <!-- 日期时间选择器 -->
          <DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='datetime'" type="datetime" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
          <!-- 日期时间围选择器 -->
          <DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='datetime-range'" type="datetimerange" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
          <!-- 日期范围选择器 -->
          <DatePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='date-range'" type="daterange" placement="bottom-end" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></DatePicker>
          <!-- 时间范围选择器 -->
          <TimePicker v-else-if="_formLayout[(rowNum - 1) * _colNum + colIndex].type==='time-range'" format="HH’mm’ss" type="timerange" :placeholder="_formLayout[(rowNum - 1) * _colNum + colIndex].placeholder" :value="_formLayout[(rowNum - 1) * _colNum + colIndex].value" @on-change="(datetime)=>{changeDateTime(datetime, _formLayout[(rowNum - 1) * _colNum + colIndex] )}"></TimePicker>
        </FormItem>
        </Col>
      </Row>
      <Row>
        <slot name="btn-area" :form-content="_formLayout"></slot>
      </Row>
    </Form>
  </div>
</template>

// commonTable 代码
<template>
  <div class="table-wrapper">
    <table class="content-table">
      <thead class="table-header" v-if="_detialData.tableHeader">
        <td :colspan="_colNum">
          <h3>{{_detialData.tableHeader}}</h3>
        </td>
      </thead>
      <tr class="table-row" v-for="(rownum, rowIndex) in _rowNum" :key="rowIndex">
        <td :class="['table-cell', {'table-bg-gray': (colnum % 2 !== 0)}]" v-for="(colnum, colIndex) in _colNum " :key="colIndex" v-if="">
          <h4 v-if="(colnum % 2 !== 0)">
            {{_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].title}}
          </h4>
          <div v-else>
            <p v-if="viewMode" class="content-text">
              {{_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value}}
            </p>
            <div v-else>
              <!-- 输入框 -->
              <Input v-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'text'" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value"></Input>
              <!-- textarea -->
              <Input v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'textarea'" type="textarea" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value"></Input>
              <!-- 日期时间选择器 -->
              <Date-picker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'datetime'" type="datetime" format="yyyy-MM-dd HH:mm" placement="top" placeholder="请选择日期..." :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></Date-picker>
              <!-- 日期时间围选择器 -->
              <DatePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='datetime-range'" type="datetimerange" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></DatePicker>
              <!-- 日期范围选择器 -->
              <DatePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='date-range'" type="daterange" placement="bottom-end" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></DatePicker>
              <!-- 时间范围选择器 -->
              <TimePicker v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type==='time-range'" format="HH’mm’ss" type="timerange" :value="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" @on-change="(datetime)=>{changeDateTime(datetime, _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)])}"></TimePicker>
              <!-- 级联菜单 -->
              <Cascader v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'cascader'" :data="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].data" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" :placeholder="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].placeholder"></Cascader>
              <!-- 下拉菜单 -->
              <Select v-else-if="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].edit_type === 'selection'" v-model="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].value" :placeholder="_dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].placeholder">
                <Option v-for="(opt, optIndex) in _dataList[Math.floor(((rownum - 1) * _colNum + colIndex) / 2)].selection" :key="optIndex" :value="opt.value">{{opt.title}}</Option>
              </Select>
            </div>
          </div>
        </td>
      </tr>
    </table>
    <slot name="btn-area" :detials="_detialData" :view-mode="_viewMode"></slot>
  </div>
</template>

项目地址:my-components 项目主页上也有相关的说明。这里就不做赘述了。

说完了思路那么还是一如既往,先来看看效果直接感受一下吧。

表单组件 commonForm

当我们传入如下数据格式的数据后,就会生成这样的表单。

  mockForm: [
    { title: '输入框', name: 'text', type: "text", placeholder: '请输入输入框内容', value: '' },
    {
      title: '地区级联菜单', name: 'cascader', type: "cascader", placeholder: '请选择地区', data: [{
        value: 'shanghai', label: '上海', children: [
          { value: 'huangpu', label: '黄浦区' },
          { value: 'huangpu', label: '普陀区' },
          { value: 'huangpu', label: '静安区' },
          { value: 'huangpu', label: '闸北区' }
        ]
      }, {
        value: 'shanghai', label: '上海', children: [
          { value: 'huangpu', label: '黄浦区' },
          { value: 'huangpu', label: '普陀区' },
          { value: 'huangpu', label: '静安区' },
          { value: 'huangpu', label: '闸北区' }
        ]
      }],
      value: []
    },
    { title: '下拉框选项', name: 'select', type: "selection", selection: [{ title: '选项一', value: '选项一' }, { title: '选项二', value: '选项二' }], placeholder: '请选择选项', value: '' },
    { title: '日期时间', name: 'datetime', type: "datetime", placeholder: '请输入日期', value: '' },
    { title: '日期时间范围', name: 'datetimerange', type: "datetime-range", placeholder: '请输入日期', value: '' },
    { title: '日期范围', name: 'daterange', type: "date-range", placeholder: '请输入日期', value: '' },
    { title: '时间范围', name: 'timerange', type: "time-range", placeholder: '请输入时间范围', value: [] },
    { title: '输入框', name: 'textarea', type: "textarea", placeholder: '请输入客户的简介', value: '' },
  ],
表单组件

表格组件 commonTable

当我们传入如下数据格式的数据后,就会生成这样的表格。点击编辑后会转为编辑模式。

  mockTable: {
    tableHeader: '表格标题',
    dataList: [
      { title: '输入框', name: 'input', value: '点击编辑后为输入框', edit_type: 'text' },
      {
        title: '级联菜单', name: 'cascader', value: ['shanghai', 'huangpu'], edit_type: 'cascader', data: [{
          value: 'shanghai', label: '上海', children: [
            { value: 'huangpu', label: '黄浦区' },
            { value: 'huangpu', label: '普陀区' },
            { value: 'huangpu', label: '静安区' },
            { value: 'huangpu', label: '闸北区' }
          ]
        }, {
          value: 'beijing', label: '北京', children: [
            { value: 'huangpu', label: '天安门' },
            { value: 'huangpu', label: '故宫' },
            { value: 'huangpu', label: '颐和园' },
            { value: 'huangpu', label: '紫禁城' }
          ]
        }]
      },
      { title: '下拉菜单', name: 'select', value: '是', edit_type: 'selection', selection: [{ title: '是', value: '是' }, { title: '否', value: '否' }] },
      { title: '日期时间', name: 'datetime', value: '2017-01-01 01:30', edit_type: 'datetime' },
      { title: '日期时间范围', name: 'datetimerange', value: '2017-01-01 01:30', edit_type: 'datetime-range' },
      { title: '日期范围', name: 'daterange', value: '2017-01-01 01:30', edit_type: 'date-range' },
      { title: '时间范围', name: 'timerange', value: '2017-01-01 01:30', edit_type: 'time-range' },      
      { title: '块级输入框', name: 'textarea', value: '这是块级输入框', edit_type: 'textarea' }
    ]
  }
表格组件 视图模式
表格组件 编辑模式

最后,现在这两个组件只是目前在我自己的项目中会比较常用。灵活性以及适用性还不够广泛(比如缺乏校验,表单的格式等),同时由于依赖iview,一些iview上组件的bug也同样会有。如果各位小伙伴有好的建议,也欢迎来交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容