editable.vue 6.74 KB
<style lang="scss">
.eagle-table-column__cell-editable {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  .el-icon-edit {
    color: rgba(151, 151, 151, 0.5);
    &:hover {
      color: $primary;
    }
  }
  .el-icon-check {
    color: $green;
  }
  .el-icon-close {
    color: $red;
  }
  .el-icon-edit,
  .el-icon-check,
  .el-icon-close {
    cursor: pointer;
    margin-left: 5px;
    font-size: 14px;
  }
}
</style>

<template>
  <el-table
    :data="tableData | tableDataFilter"
    :size="tableSize"
    v-bind="bindProps"
    v-on="$listeners"
    @header-click="onHeaderClick"
    @cell-click="onCellClick"
    @cell-dblclick="onCellDblclick"
  >
    <slot name="left"></slot>
    <template v-for="(item, index) in columns">
      <el-table-column v-bind="item" :key="index">
        <slot :name="`header-${item.prop}`" slot="header"></slot>
        <template #default="{ row, column, $index }">
          <cell-editor
            :disabled="item.editalways || editall || disabled || item.editable === false"
            :editable="item.editalways || editall || (item.editable !== false && row.$editor && row.$editor.includes(item.prop))"
            :component="item.component"
            :value="row[column.property]"
            @input="value => onCellInput(value, row, column, $index)"
            @edit-click="setRowEditor(row, column, $index)"
            @edit-confirm="value => onEditConfirm(value, row, column, $index)"
          >
            <template v-if="$scopedSlots[`editor-${item.prop}`]" slot="editor">
              <slot :name="`editor-${item.prop}`" :value="row[column.property]" :row="row" :index="$index" :onInput="value => onCellInput(value, row, column, $index)"></slot>
            </template>
            <template v-if="$scopedSlots[`cell-${item.prop}`]">
              <slot :name="`cell-${item.prop}`" :value="row[column.property]" :row="row" :index="$index"></slot>
            </template>
            <template v-else-if="item.render && typeof item.render === 'function'" #default="{ row, column, $index }">
              <cell-render :item="item" :value="get(row, item.prop)" :row="row" :column="column" :index="$index"></cell-render>
            </template>
          </cell-editor>
        </template>
      </el-table-column>
    </template>
    <slot></slot>
    <slot name="append"></slot>
  </el-table>
</template>

<script>
import TableNormal from './normal';
import tableProps from './props';
import { cloneDeep, get, set } from '../utils';

export default {
  name: 'TableEditable',
  extends: TableNormal,
  components: {
    cellEditor: {
      props: {
        value: [String, Number, Array, Object, Boolean],
        component: { type: String, default: 'el-input' },
        editable: Boolean,
        disabled: Boolean,
      },
      watch: {
        editable(val) {
          if (!this.disabled && val && this.component === 'el-input') {
            this.$nextTick(() => {
              this.$children[0] && this.$children[0].focus && this.$children[0].focus();
            });
          }
        },
      },
      render(h) {
        if (this.editable) {
          let editorRender = [
            h(this.component, {
              props: { value: this.value, size: 'mini' },
              on: {
                input: value => {
                  this.$emit('input', value);
                },
              },
            }),
          ];
          if (this.$scopedSlots.editor) {
            editorRender = [this.$scopedSlots.editor()];
          }
          if (!this.disabled) {
            const handlerItems = [h('i', { attrs: { title: '确定', class: 'el-icon-check' }, on: { click: () => this.$emit('edit-confirm', this.value) } })];
            // handlerItems.push(h('i', { attrs: { title: '取消', class: 'el-icon-close' }, on: { click: () => this.$emit('edit-confirm') } }));
            const handler = h('span', handlerItems);
            editorRender.push(handler);
          }
          return h('span', { class: 'eagle-table-column__cell-editable' }, editorRender);
        }
        let valueRender = [h('span', this.value)];
        if (this.$scopedSlots.default) {
          valueRender = [this.$scopedSlots.default()];
        }
        if (!this.disabled) {
          valueRender.push(h('i', { attrs: { title: '编辑', class: 'el-icon-edit' }, on: { click: () => this.$emit('edit-click') } }));
        }
        return h('span', { class: 'eagle-table-column__cell-editable' }, valueRender);
      },
    },
  },
  props: {
    value: {
      type: Array,
      default() {
        return [];
      },
    },
    columns: {
      type: Array,
      default() {
        return [];
      },
    },
    editall: Boolean,
    clickable: Boolean,
    disabled: Boolean,
    ...tableProps,
  },
  watch: {
    value(val) {
      this.tableData = val || [];
    },
    data(val) {
      this.tableData = val || [];
    },
    tableData(val) {
      this.$emit('input', val || []);
    },
  },
  data() {
    return {
      tableData: this.value,
    };
  },
  filters: {
    tableDataFilter(value) {
      return value.map((item, index) => ({ ...item, $index: index }));
    },
  },
  methods: {
    onHeaderClick() {
      if (this.clickable) {
        this.cancelEditCell();
      }
    },
    onCellClick(row, column) {
      if (this.clickable) {
        const prop = column.property;
        let tableData = cloneDeep(this.tableData);
        tableData.forEach((item, index) => {
          if (!(index === row.$index && item.$editor && item.$editor.includes(prop))) {
            item.$editor = [];
          }
        });
        this.tableData = tableData;
      }
    },
    onCellDblclick(row, column) {
      if (this.clickable) {
        this.setRowEditor(row, column, row.$index);
      }
    },
    setRowEditor(row, column, index) {
      this.cancelEditCell();
      let tableRow = this.tableData[index];
      if (tableRow) {
        if (tableRow.$editor) {
          tableRow.$editor = [...tableRow.$editor, column.property];
        } else {
          tableRow.$editor = [column.property];
        }
        this.$set(this.tableData, index, tableRow);
      }
    },
    onEditConfirm(value, row, column, index) {
      this.$emit('cell-edit-confirm', { row, index, prop: column.property, value });
      this.cancelEditCell();
    },
    cancelEditCell() {
      this.tableData = this.tableData.map((item, index) => {
        const newItem = cloneDeep(item);
        delete newItem.$index;
        delete newItem.$editor;
        return newItem;
      });
    },
    onCellInput(value, row, column, index) {
      const tableData = cloneDeep(this.tableData);
      const tableRow = tableData[index];
      set(tableRow, column.property, value);
      tableData[index] = tableRow;
      this.$set(this.tableData, index, tableRow);
    },
  },
};
</script>