Design an editable table component

Preface

What is an editable table? To put it simply, it is to perform form operations in a table and perform additions, deletions, modifications and queries. This is particularly common in some backend management systems.
Insert image description here
Today we design a form component based on vue2+ . element-ui(The complete code is not involved. If you want to use the complete function, you can see the link at the bottom)

Start designing

1. Form component

First, let's think about how the form elements in the table should be implemented. When the user uses it, we hope that the user will pass in a specified type to automatically match the corresponding form component. Then we should integrate all types and give them a type name respectively.

element-uiOf course, in order to retain the already implemented properties and methods for them to the greatest extent , formElethey must receive an object, which contains the component type it wants to display . We can directly bind the properties to some of element-uithe original property methods using $attrsOn the form component of element-ui.

We name this form element component formEleand retain element-uithe implemented properties and methods for them to the greatest extent possible. Of course, we can also modify the default values ​​of some commonly used attributes, such as turning on clearable to clear attributes and setting the default value of placeholder.

Below is part of the code

<template>
  <el-input
    v-if="formType === 'input'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :clearable="clearable"
    :placeholder="placeholder"
  >
    <template v-for="(value, name) in $slots" #[name]>
      <slot :name="name"> </slot>
    </template>
  </el-input>
  <el-input-number
    v-else-if="formType === 'inputNumber'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :placeholder="placeholder"
  >
    <template v-for="(value, name) in $slots" #[name]>
      <slot :name="name"> </slot>
    </template>
  </el-input-number>
  <el-select
    v-else-if="formType === 'select'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :clearable="clearable"
    :placeholder="placeholder"
  >
    <el-option
      v-for="item in options"
      v-bind="item"
      :key="item.value"
    ></el-option>
  </el-select>
  <el-date-picker
    v-else-if="formType === 'datePicker'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :placeholder="placeholder"
    :endPlaceholder="endPlaceholder"
    :startPlaceholder="startPlaceholder"
  >
  </el-date-picker>
  <el-time-select
    v-else-if="formType === 'timeSelect'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :placeholder="placeholder"
  >
  </el-time-select>
  <el-time-picker
    v-else-if="formType === 'timePicker'"
    v-model="localValue"
    v-bind="$attrs"
    v-on="$listeners"
    :placeholder="placeholder"
    :endPlaceholder="endPlaceholder"
    :startPlaceholder="startPlaceholder"
  >
  </el-time-picker>
  <el-switch
    v-else-if="formType === 'switch'"
    v-model="localValue"
    v-bind="$attrs"
    :placeholder="placeholder"
  >
  </el-switch>
  <el-cascader
    v-else-if="formType === 'cascader'"
    v-model="localValue"
    :options="options"
    ref="cascader"
    v-bind="$attrs"
    v-on="$listeners"
    :placeholder="placeholder"
  ></el-cascader>
</template>

<script>
export default {
      
      
  name: 'ClFormEle',
  props: {
      
      
    // 表单类型
    formType: {
      
      
      type: String,
      default: 'input'
    },
    modelValue: null,
    options: {
      
      
      type: Array,
      default: () => []
    }
  },
  model: {
      
      
    prop: 'modelValue',
    event: 'editModelValue'
  },
  computed: {
      
      
    localValue: {
      
      
      get() {
      
      
        return this.modelValue
      },
      set(val) {
      
      
        this.$emit('editModelValue', val)
      }
    },
    clearable() {
      
      
      return this.$attrs.clearable === false ? false : true
    },
    placeholder() {
      
      
      let text = '请选择'
      if (this.formType === 'input') {
      
      
        text = '请输入'
      }
      return this.$attrs.placeholder || text + (this.$attrs.label || '')
    },
    rangeSeparator() {
      
      
      return this.$attrs.rangeSeparator || '至'
    },
    startPlaceholder() {
      
      
      if (this.formType === 'datePicker') {
      
      
        return this.$attrs.startPlaceholder || '开始日期'
      } else {
      
      
        return this.$attrs.startPlaceholder || '开始时间'
      }
    },
    endPlaceholder() {
      
      
      if (this.formType === 'datePicker') {
      
      
        return this.$attrs.startPlaceholder || '结束日期'
      } else {
      
      
        return this.$attrs.startPlaceholder || '结束时间'
      }
    }
  },
  methods: {
      
      
    // 获取级联组件的回显值
    getCasLabelcader() {
      
      
      this.$nextTick(() => {
      
      
        return this.$refs.cascader?.inputValue
      })
    }
  }
}
</script>

<style lang="less" scoped>
.el-input {
      
      
  width: 100%;
  height: 30px;
}
.el-select {
      
      
  width: 100%;
}
.el-date-editor {
      
      
  width: 100%;
}
</style>

It should be noted that we also need to element-uireserve the slot location of the component.

2. Consider table components

First of all, we have to think that we may use the verification and other functions of the form component, so el-formthe table component must be wrapped in the outermost layer.

The second is the selection of the binding value of the model on the form component. It can be expected that the data we finally bind must be in an array format. Because the table will definitely not have only one piece of data. But we cannot bind an array to the model attribute of the form, so we can encapsulate the array into the format of an array object.

 <!-- modelValueCom 为表格数据 -->
<el-form ref="formRef" :model="{ formData: modelValueCom }">

Next we will process the column data. The data of the column must also be in an array structure. Each column is divided into two sections, one is el-table-columnthe attribute value used on the table, and the other is the attribute value of the internal form content. To facilitate the distinction, we will use some data formats:

[
  {
    
    
    prop: 'name',
    label: '姓名',
    minWidth: '200px',
    formEle: {
    
    
      formType: 'input'
    }
  }
]

The above formEle object is the attribute value that needs to be used on the form component in the future, while other attributes are directly applied to it el-table-column.

<el-table-column
	v-for="columItem in columList"
	:key="columItem.prop"
	v-bind="getColumnAttr(columItem)"
>

The function of the getColumnAttr method is to remove the formEle attribute

Then there is the use of form components. Since we need the form verification method, we need el-form-itemcomponents to wrap the form components, and the most important propproperties on the components can be obtained directly from the column data above. Note that because the data is an array, we need to use subscripts to bind it to For a specific piece of data, labelwe can directly omit the attribute because we already have a table header to display it.

<el-form-item
   v-if="row.isEdit"
   :prop="'formData.' + $index + '.' + columItem.prop"
   :rules="columItem.formEle?.rules"
>

Speaking of the table header, we may need to think of adding a required check indicator for it. We add the showRequiredIcon attribute to formEle to determine whether the required check needs to be displayed.
Insert image description here

<el-table-column
  v-for="columItem in columList"
  :key="columItem.prop"
  v-bind="getColumnAttr(columItem)"
>
  <template #default="{ row, $index }">
    <el-form-item
      v-if="row.isEdit"
      :prop="'formData.' + $index + '.' + columItem.prop"
      :rules="columItem.formEle?.rules"
    >
      <ClFormEle
        v-model="row[columItem.prop]"
        v-bind="columItem.formEle"
        v-on="eventMap[columItem.prop]"
        :label="columItem.label"
      ></ClFormEle>
    </el-form-item>
    <!-- getLabel方法用于回显,解析一些下拉,级联,时间等 -->
    <span v-else>{
   
   {
      getLabel(
        getFormType(columItem),
        columItem,
        row[columItem.prop],
        $index
      )
    }}</span>
  </template>
  <template #header>
    <span v-if="columItem.formEle?.showRequiredIcon" class="required_icon"
      >*</span
    >
    {
   
   { columItem.label }}
  </template>
</el-table-column>

Finally, we should also need a column of operation buttons to modify, save, delete, etc. row data. Modification and saving means modifying the isEdit attribute of the table data to switch between form element and span display.

<el-table-column v-bind="btnColCpd">
  <template #default="{ row, $index: index, column }">
    <slot name="endColumn" :data="{ row, index, column }">
      <el-button
        v-if="!row.isEdit"
        type="text"
        icon="el-icon-edit"
        @click.native.stop="rowEdit(index)"
        >修改</el-button
      >
      <el-button
        v-else
        type="text"
        icon="el-icon-check"
        @click.native.stop="saveRow(index)"
        >保存</el-button
      >
      <el-popconfirm
        v-if="isDelBtnTip"
        :title="delBtnTip"
        @confirm="delRow(index)"
      >
        <el-button
          type="text"
          icon="el-icon-delete"
          class="del_btn_text"
          slot="reference"
          >删除</el-button
        >
      </el-popconfirm>
      <el-button
        v-else
        type="text"
        icon="el-icon-delete"
        class="del_btn_text"
        slot="reference"
        @click.native.stop="delRow(index)"
        >删除</el-button
      >
    </slot>
  </template>
</el-table-column>

btnColCpd is a property configuration object about the last column. We can use the properties of the el-table-column component in element-ui to control the last column.

At this point, a simple form component structure has been basically implemented.

Of course, the above design still has many flaws, such as how to bind the form component methods, how to throw out each form component slot, and whether the form verification can be triggered when leaving the row. If you want to use the component What to do if formEle does not contain it, etc. If you want to implement a complete form component, these must be considered. I have encapsulated a relatively complete form component, which you can view at TableForm .

Guess you like

Origin blog.csdn.net/qq_44473483/article/details/134989279