vue可编辑表格

内容包含:校验。下拉框。输入框。日期控件

效果图

5062a15a66fa464396f743ab6ef2ecf1.png

7cfa845d2ca94b7f924a598afd4e5ac0.png

 53028aa956d94d1dbed8b5f5d19e6797.png

1.代码目录

05ffd5a8c8ea457eabf246b35e2681b7.png

2.index.js

import SjjEditable from './src/editable.vue'
// import Vue from 'vue'

SjjEditable.install = function (Vue) {
  Vue.component(SjjEditable.name, SjjEditable)
}

export default SjjEditable

 3.utilsjs


export const getRowIndetity = (row, rowKey) => {
  if (!row) throw new Error('row is required when get row identity')
  if (typeof rowKey === 'string') {
    if (rowKey.indexOf('.') < 0) {
      return row[rowKey]
    }
    let key = rowKey.split('.')
    let current = row
    for (let i = 0; i < key.length; i++) {
      current = current[key[i]]
    }
    return current
  } else if (typeof rowKey === 'function') {
    return rowKey.bind(null, row)
  }
}

4.editable-column.vue

<template>
  <el-table-column v-if="isIndexTypeColumn(type)" :prop="prop" :type="type" :label="label" :width="width" :min-width="minWidth"/>

  <el-table-column v-else :prop="prop" :type="type" :label="label" :width="width" :min-width="minWidth" >
    <template slot-scope="scope">
      <el-form v-if="type && editing && editing[scope.$index] && editing[scope.$index][prop]" :model="scope.row" :rules="rules" @validate="handleValidate" :ref="`form_${scope.$index}_${prop}`">
        <el-form-item :prop="type === 'select' ? prop.replace(/(\_*)(text|txt)/ig, '') : prop" class="el-editable-form-item">
          <el-input autofocus v-if="type === 'text' || type === 'textarea'" :type="type" v-model="scope.row[prop]" class="el-editable-element" @change="handleChange"/>
          <el-date-picker v-else-if="type === 'date' || type === 'datetime'" v-model="scope.row[prop]" :type="type" class="el-editable-element" @change="handleChange" :format="format" :value-format="valueFormat"/>
          <el-select v-else-if="type === 'select'" v-model="scope.row[prop.replace(/(\_*)(text|txt)/ig, '')]" class="el-editable-element" @change="handleChange">
            <el-option v-for="(o,i) in options" :key="i" :value="o.value" :label="o.label || o.text" :disabled="o.disabled ? true:false"/>
          </el-select>
          <sjj-currency v-else-if="type === 'currency'" v-model="scope.row[prop]" class="el-editable-element" @change="handleChange"/>
        </el-form-item>
      </el-form>
      <div v-else :class="editedCell&&editedCell[scope.$index]&&editedCell[scope.$index][prop] ? 'is-edited' : ''">{{ scope.row[prop] }}</div>
    </template>
  </el-table-column>
</template>

<script>
import sjjCurrency from '../../currency/index.js'
import merge from 'element-ui/src/utils/merge'
import { getRowIndetity } from './utils.js'
export default {
  name: 'SjjEditableColumn',
  props: {
    type: {
      type: String,
      default: ''
    },
    label: String,
    className: String,
    labelClassName: String,
    prop: String,
    width: {},
    minWidth: {},
    index: [Number, Function],
    options: Array,
    format: { // 显示在输入框中的格式,默认为yyyy-MM-dd
      type: String,
      default: 'yyyy-MM-dd'
    },
    valueFormat: { // 绑定值得格式,默认为yyyy-MM-dd
      type: String,
      default: 'yyyy-MM-dd'
    }
  },
  components: {
    sjjCurrency
  },
  data () {
    return {
      validates: false,
      editedCell: {}, // 编辑成功过的cell们
      validatedRow: {}, // 已编辑的行
      validated: {} // 已编辑的cell
    }
  },
  mounted () {
    this.validated = this.$parent.$parent.edited
    this.validatedRow = this.$parent.$parent.editedRow
  },
  computed: {
    editing () {
      return this.$parent.$parent.editing || {}
    },
    tableData () {
      return this.$parent.$parent.tableData || []
    },
    originData () {
      return this.$parent.$parent.originData || []
    },
    rules () {
      return this.$parent.$parent.$props.rules || []
    }
  },
  methods: {
    isIndexTypeColumn (type) {
      return type === 'index' // ['selection', 'index', 'expand']
    },
    isDataColumn (type) {
      return ['text', 'textarea', 'date', 'datetime', 'select', 'currency'].indexOf(type) > -1
    },
    handleValidate (prop, validateFlag) {
      this.$parent.$parent.setValidateFlag(validateFlag)
      if (validateFlag) {
        this.editedCell = merge(this.editedCell, this.editing)
        let property = prop
        for (let rowIdx in this.editing) {
          if (this.$props.type === 'select') {
            property = this.$props.prop // property带text,prop是rules的属性
          }
          this.setEditedRow(rowIdx, prop, property)
        }
      }
    },
    handleChange (value) {
      for (let rowIdx in this.editing) {
        this.$emit('change', rowIdx, this.$props.prop, value)
      }
    },
    // validateColumn (rowIdx, value) {
    //   let prop = this.$props.prop
    //   if (this.$props.type === 'select') {
    //     prop = prop.replace(/(\_*)(text|txt)/ig, '')
    //   }
    //   this.$refs[`form_${rowIdx}_${prop}`].validate((valid, prop) => {})
    // },
    setEditedRow (rowIdx, prop, property) {
      let value = this.tableData[rowIdx][prop]
      let textValue = value
      if (this.$props.type === 'select') {
        let arr = this.$props.options.filter(o => {
          return o.value === value
        })
        if (arr.length) {
          textValue = arr[0].label || arr[0].text
        }
      }
      this.tableData[rowIdx][property] = textValue

      if (value !== this.originData[rowIdx][prop]) {
        let obj = {}
        obj[prop] = value
        if (this.$props.type === 'select') {
          obj[property] = textValue
        }
        if (this.validated.hasOwnProperty(rowIdx)) {
          this.validated[rowIdx] = merge(this.validated[rowIdx], obj)
        } else {
          let rowId = getRowIndetity(this.tableData[rowIdx], this.$parent.$parent.$props.rowKey)
          obj[this.$parent.$parent.$props.rowKey] = rowId
          this.validated[rowIdx] = obj
        }

        this.validatedRow[rowIdx] = merge({}, this.tableData[rowIdx])
      } else {
        if (this.editedCell[rowIdx]) {
          delete this.editedCell[rowIdx][property]
        }

        if (this.validated.hasOwnProperty(rowIdx)) {
          let equalFlag = true
          for (let k in this.validated[rowIdx]) {
            if (k === prop || k === property) {
              continue
            }
            if (this.validated[rowIdx][k] !== this.originData[rowIdx][k]) {
              equalFlag = false
              break
            }
          }
          if (equalFlag) {
            delete this.validated[rowIdx]
          } else {
            delete this.validated[rowIdx][prop]
            delete this.validated[rowIdx][property]
          }
        }
        if (this.validatedRow.hasOwnProperty(rowIdx)) {
          let equalFlag = true
          for (let k in this.validatedRow[rowIdx]) {
            if (k === prop || k === property) {
              continue
            }
            if (this.validatedRow[rowIdx][k] !== this.originData[rowIdx][k]) {
              equalFlag = false
              break
            }
          }
          if (equalFlag) {
            delete this.validatedRow[rowIdx]
          } else {
            this.validatedRow[rowIdx] = merge({}, this.tableData[rowIdx])
          }
        }
      }
      this.$parent.$parent.setEdited(this.validated)
      this.$parent.$parent.setEditedRow(this.validatedRow)
    }
  }
}
</script>

<style scoped>
  .is-edited {
    color: red;
  }
/* .sjj-editable-form-item {
  margin: 0;
}
.sjj-editable-element.el-input {
  line-height: normal;
}
.sjj-editable-form-item  /deep/ .el-form-item__content {
  line-height: normal;
}
.sjj-editable-form-item  /deep/ .el-form-item__error {
  position: relative;
}
.sjj-editable-form-item  /deep/ .el-input__inner {
  height: 0;
  line-height: 1;
  padding: 15px;
} */
/* >>> .el-table__row {
  height: 50px;
}
>>> .el-table td, .el-table th {
  padding: 4px 0;
} */
</style>

5.editable.vue

<template>
  <div class="el-editable">
    <el-table border :data="tableData" @cell-dblclick="handleCellDblClick" :row-key="rowKey" :height="height">
      <slot></slot>
    </el-table>
  </div>
</template>

<script>
import Vue from 'vue'
import merge from 'element-ui/src/utils/merge'
import { getRowIdentity } from 'element-ui/packages/table/src/util'
export default {
  name: 'SjjEditable',
  props: {
    data: {
      type: Array,
      default: Function,
      required: true
    },
    rowKey: {
      type: String,
      required: true
    },
    height: [String, Number],
    rules: {
      type: Object,
      required: true
    }
  },
  data () {
    return {
      originData: [], // 原始表格数据
      editing: {}, // 正在编辑对象
      validateFlag: null,
      editedRow: {}, // 编辑成功的row
      deditedRowData: [], // 编辑成功的行数据集
      edited: {}, // 编辑成功的cell
      editedData: [], // 编辑成功的单元格数据集
      slotChildren: []
    }
  },
  created () {
    this.setOriginData(this.data)
  },
  computed: {
    tableData () {
      if (!this.data) {
        return []
      }
      let arr = this.data.filter((d, idx) => {
        return d
      })
      return arr
    }
  },
  watch: {
    data (newVal, oldVal) {
      this.setOriginData(this.data)
    }
  },
  methods: {
    setOriginData (val) {
      if (!val) {
        this.originData = []
      } else {
        this.originData = val.map((d, idx) => {
          let obj = merge({}, d)
          return obj
        })
      }
    },
    setValidateFlag (val) {
      this.validateFlag = val
    },
    setEditedRow (val) {
      this.editedRow = val
    },
    setEdited (val) {
      this.edited = val
    },
    handleCellDblClick (row, column, cell, event) {
      // this.slotChildren = []
      // for (let i = 0; i < this.$slots.default.length; i++) {
      //   console.log(this.$slots.default[i].elm.nodeType)
      //   if (this.$slots.default[i].elm.nodeType !== 3) {
      //     this.slotChildren.push(this.$slots.default[i]) // 获得那些插入的按钮
      //   }
      // }
      // console.log(this.slotChildren[2])
      //if (Object.keys(this.editing).length && !this.validateFlag) {
        //return
      //}
      this.editing = {}
      let rowIdx = null
      for (let i = 0; i < this.tableData.length; i++) {
        let d = this.tableData[i]
        if (getRowIdentity(d, this.rowKey) === getRowIdentity(row, this.rowKey)) {
          rowIdx = i
        }
      }
      if (rowIdx !== null) {
        let obj = {}
        obj[column.property] = true
        Vue.set(this.editing, rowIdx, obj)
      }
    },
    getEditedRowData () {
      this.editedRowData = []
      for (let rowIdx in this.editedRow) {
        this.editedRowData.push(this.editedRow[rowIdx])
      }
      return this.editedRowData
    },
    getEditedData () {
      this.editedData = []
      for (let rowIdx in this.edited) {
        this.editedData.push(this.edited[rowIdx])
      }
      return this.editedData
    },
    isPass () {
      return this.validateFlag === null || this.validateFlag === true
    }
  }
}
</script>

6.使用

<template>
  <div class="phone">
    <el-button @click="getEditedRowData">查看已编辑行数据</el-button>
    <el-button @click="getEditedData">查看已编辑单元格数据</el-button>
    <sjj-editable :data="tableData" row-key="rowId" :rules="rules" ref="tb" style="margin-top: 20px">
      <sjj-editable-column type="index" label="序号" width="100"></sjj-editable-column>
      <sjj-editable-column prop="date" label="日期" type="date" @change="handleChangeDate"></sjj-editable-column>
      <sjj-editable-column prop='name' label="姓名" type="text" @change="handleChangeName"></sjj-editable-column>
      <sjj-editable-column prop="provinceText" label="城市" type="select" :options="options"></sjj-editable-column>
      <sjj-editable-column prop='address' label="地址" type="textarea"></sjj-editable-column>
      <sjj-editable-column prop='zip' label="邮编"></sjj-editable-column>
    </sjj-editable>

    <div style="margin-bottom: 20px;">
      <h3>Editable Attributes</h3>
      <el-table
        :data="tableData2"
        stripe
        border
        style="width: 100%">
        <el-table-column
          prop="parameter"
          label="参数"
          width="180">
        </el-table-column>
        <el-table-column
          prop="instructions"
          label="说明">
        </el-table-column>
        <el-table-column
          prop="type"
          label="类型"
          width="180">
        </el-table-column>
        <el-table-column
          prop="optionalValue"
          label="可选值"
          width="180">
        </el-table-column>
        <el-table-column
          prop="defaultValue"
          label="默认值"
          width="180">
        </el-table-column>
      </el-table>
    </div>

    <div style="margin-bottom: 20px;">
      <h3>Editable-Column Attributes</h3>
      <el-table
        :data="tableData3"
        stripe
        border
        style="width: 100%">
        <el-table-column
          prop="parameter"
          label="参数"
          width="180">
        </el-table-column>
        <el-table-column
          prop="instructions"
          label="说明">
        </el-table-column>
        <el-table-column
          prop="type"
          label="类型"
          width="180">
        </el-table-column>
        <el-table-column
          prop="optionalValue"
          label="可选值"
          width="180">
        </el-table-column>
        <el-table-column
          prop="defaultValue"
          label="默认值"
          width="180">
        </el-table-column>
      </el-table>
    </div>
    <div style="margin-bottom: 20px;">
      <h3>Editable Methods</h3>
      <el-table
        :data="tableData4"
        stripe
        border
        style="width: 100%">
        <el-table-column
          prop="name"
          label="方法名"
          width="180">
        </el-table-column>
        <el-table-column
          prop="instructions"
          label="说明">
        </el-table-column>
        <el-table-column
          prop="parameter"
          label="参数"
          width="180">
        </el-table-column>
        <el-table-column
          prop="valueType"
          label="返回值类型"
          width="180">
        </el-table-column>
        <el-table-column
          prop="valueFormat"
          label="返回值格式"
          width="180">
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script>
import SjjEditable from '../../components/editable/index.js'
import SjjEditableColumn from '../../components/editable/src/editable-column'
export default {
  name: 'PhoneS',
  components: {
    SjjEditable,
    SjjEditableColumn
  },
  data () {
    return {
      options: [
        {
          value: '1',
          text: '上海'
        },
        {
          value: '2',
          text: '北京'
        },
        {
          value: '3',
          text: '杭州'
        }
      ],
      tableData: [
        {
          rowId: 'R001',
          date: '2021-08-17',
          name: '王小虎',
          province: '1',
          provinceText: '上海',
          city: '普陀',
          address: '上海市普陀区',
          zip: 200320,
          tag: '家'
        },
        {
          rowId: 'R002',
          date: '2021-08-18',
          name: '王小',
          province: '2',
          provinceText: '北京',
          city: '朝阳',
          address: '北京朝阳',
          zip: 200330,
          tag: '公司'
        }
      ],
      rules: {
        name: [
          { required: true, message: '请输入姓名', trigger: 'blur' },
          { min: 1, max: 3, message: '姓名不超过3个字符', trigger: 'blur' }
        ],
        address: [
          { required: true, message: '请输入地址', trigger: 'blur' }
        ],
        province: [
          { required: true, message: '请选择城市', trigger: 'change' }
        ],
        date: [
          { required: true, message: '请选择日期', trigger: 'change' }
        ]
      },
      tableData2: [
        {
          parameter: 'data',
          instructions: '表格数据,必输',
          type: 'Array',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'row-key',
          instructions: '行数据的key,必输',
          type: 'String',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'rules',
          instructions: '验证规则,必输',
          type: 'Object',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'height',
          instructions: '表格高度,默认为自动高度,如果height为number类型,单位px;如果height为String类型,则这个高度会设置为Table的style.height的值,Table的高度则会受控于外部样式',
          type: 'String/Number',
          optionalValue: '-',
          defaultValue: '-'
        }
      ],
      tableData3: [
        {
          parameter: 'type',
          instructions: '列属性,其中,不设置/index表示双击不可编辑',
          type: 'String',
          optionalValue: 'index/text/textarea/select/date/datetime/currency',
          defaultValue: '-'
        },
        {
          parameter: 'index',
          instructions: '如果设置了type=index,可以通过传递index属性来定义索引',
          type: 'String/Function(index)',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'options',
          instructions: '如果设置了type=select,通过传递options属性定义选项列表',
          type: 'Array',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'format',
          instructions: '如果设置了type=date,type=datetime,通过传递format实现定义显示在输入框中的格式',
          type: 'String',
          optionalValue: '-',
          defaultValue: 'yyyy-MM-dd'
        },
        {
          parameter: 'value-format',
          instructions: '如果设置了type=date,type=datetime,通过传递value-format属性定义绑定值得格式',
          type: 'String',
          optionalValue: '-',
          defaultValue: 'yyyy-MM-dd'
        },
        {
          parameter: 'label',
          instructions: '显示的标题',
          type: 'String',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'prop',
          instructions: '对应列内容的字段名',
          type: 'String',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'width',
          instructions: '固定列宽',
          type: 'String',
          optionalValue: '-',
          defaultValue: '-'
        },
        {
          parameter: 'min-width',
          instructions: '最小列宽,与width的却别是width是固定的,min-width会把剩余宽度按比例分配给设置了min-width的列',
          type: 'Array',
          optionalValue: '-',
          defaultValue: '-'
        }
      ],
      tableData4: [
        {
          name: 'getEditedRowData',
          instructions: '获取编辑过的整行数据集',
          parameter: '-',
          valueType: 'Array',
          valueFormat: '[ { data的某行 }, ... ]'
        },
        {
          name: 'getEditedData',
          instructions: '获取编辑过的行以及列的数据集',
          parameter: '-',
          valueType: 'Array',
          valueFormat: '[ { rowKey: value, prop1: cellValue1, prop2: cellValue2, ... }, ... ]'
        }
      ]
    }
  },
  methods: {
    getEditedRowData () {
      alert(JSON.stringify(this.$refs.tb.getEditedRowData()))
    },
    getEditedData () {
      alert(JSON.stringify(this.$refs.tb.getEditedData()))
    },
    handleChangeDate (rowIdx, prop, value) {
      console.log(`handle change date: ${rowIdx}--${prop}--${value}`)
    },
    handleChangeName (rowIdx, prop, value) {
      console.log(`handle change name: ${rowIdx}--${prop}--${value}`)
    }
  }
}
</script>

<style scoped>
/* .explain {
  margin: 20px 0;
}
.explain span {
  color: #409eff;
}
.phone /deep/ .el-card__body {
  height: 250px;
}
.phone /deep/ .el-row {
  margin-bottom: 20px;
} */
</style>

方法

fc39bafef950477f889c2e8f54af7ba8.png

 b42369dcb3424665aa7ee9935f79fa3c.png

56846b4892b04560ae1c442e668d6d0d.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shuleijia

您的鼓励是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值