editable.vue 10.1 KB
<style>
.eagle-table {
  width: 100%;
}
</style>

<template>
  <div @keyup.enter="tableEditCell = {}">
    <div style="padding-bottom: 10px;">
      <el-button @click="handleNew" size="mini" v-if="!rowEdit">新增</el-button>
      <el-button @click="handleEdit" size="mini" v-if="!rowEditable && tableSelection.length > 0">编辑</el-button>
      <el-button @click="handleSave" size="mini" type="primary" v-if="rowEditable">完成</el-button>
      <el-button @click="handleCancel" size="mini" type="plain" v-if="rowNew && !rowEdit">取消</el-button>
      <el-button @click="handleDelete" size="mini" type="danger" v-if="tableSelection.length > 0">删除</el-button>
    </div>
    <el-table class="eagle-table" ref="table" :data="tableData" v-bind="{ size: 'small', ...tableProps }"
      v-on="tableEvents" @cell-dblclick="onCellDblclick" @selection-change="onSelectionChange"
    >
      <el-table-column type="selection" :selectable="r => rowNew ? r.$new : true"></el-table-column>
      <!-- 默认表格插槽 -->
      <slot name="default"></slot>
      <!-- 根据配置列表生成表格列 -->
      <template v-if="tableList && tableList.length > 0">
        <template v-for="(item, index) in tableList">
          <template v-if="bindItemVisible(item.visible)">
            <!-- 如果有表格列具名插槽 -->
            <slot v-if="$scopedSlots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot>
            <!-- 默认表格列渲染 -->
            <el-table-column v-else v-bind="item" :prop="item.fullKey || item.key" :key="index" :min-width="minWidth || item.minWidth || item['min-width'] || (editable ? 140 : undefined)">
              <template #default="{ row, column, $index }">
                <cell-editable
                  :editable="row.$editable || (editable && (tableEditCell.index === row.$index && tableEditCell.key === (item.agentKey || item.fullKey || item.key)))"
                  :row="row" :item="item" @update="onCellUpdate" @done="onCellUpdateDone" @cancel="onCellUpdateCancel" :btn-visible="!row.$editable"
                >
                  <!-- 如果有表格列值渲染具名插槽 -->
                  <slot v-if="$scopedSlots[`value-${item.keyPath.join('-')}`]" :name="`value-${item.keyPath.join('-')}`" v-bind="item"
                    :row="row" :value="$_get(row, item.fullKey)" :column="column" :index="$index"
                  ></slot>
                  <!-- 如果表格列配置了值渲染参数 -->
                  <cell-value-render v-else-if="item.render" :row="row" :column="column" :index="$index" :item="item" />
                </cell-editable>
              </template>
            </el-table-column>
          </template>
        </template>
      </template>
      <!-- 已生成列表后的追加列插槽 -->
      <slot name="column-append"></slot>
      <!-- 末尾列插槽 -->
      <slot name="column-end"></slot>
    </el-table>
  </div>
</template>

<script>
import { cloneDeep, get, set } from '../form-new/util';
import CellEditable from './cell-editable';
import CellValueRender from './cell-value-render';

const listHasKey = (list, name) => {
  let result = false;
  for (const row of list) {
    if (row[name]) {
      result = true;
      break;
    }
  }
  return result;
}

export default {
  name: 'TableNew',
  components: {
    CellEditable,
    CellValueRender,
  },
  props: {
    // 用于实例化本组件绑定v-model的值
    value: Array,
    // 配置列表
    list: {
      type: Array,
      required: true
    },
    // 表格参数
    tableProps: {
      type: Object,
      default() { return {} }
    },
    // 表格事件
    tableEvents: Object,
    // 是否可编辑
    editable: Boolean,
    // 列宽
    minWidth: Number,
  },
  data() {
    return {
      tableList: [], // 表格配置列表
      tableData: [], // 表格数据
      tableRowTemplate: { $editable: true }, // 行数据模板
      tableEditCell: {}, // 正在编辑的单元格
      tableSelection: [], // 表格已选中
    };
  },
  computed: {
    // 表格实体
    instance: {
      get() { return this.$refs.table; }
    },
    // 行编辑状态
    rowEditable() {
      return listHasKey(this.tableData, '$editable');
    },
    // 存在新行
    rowNew() {
      return listHasKey(this.tableData, '$new');
    },
    // 存在编辑行
    rowEdit() {
      return listHasKey(this.tableData, '$edit');
    }
  },
  watch: {
    value: {
      handler(val = []) {
        this.tableData = val.map((o, i) => {
          return { ...o, $index: i, $editable: undefined, $new: undefined };
        });
      },
      immediate: true
    },
    list: {
      handler(val) {
        // 深度克隆传入的列表,避免原始值被修改
        const newList = cloneDeep(this.list);
        // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key
        const generateFullKey = (list, parentKey) => {
          list.forEach(item => {
            if (item.group && item.list) {
              if (item.group.key) {
                item.fullKey = `${parentKey ? `${parentKey}.${item.group.key}` : item.group.key}`;
              } else {
                item.fullKey = parentKey || item.key;
              }
              generateFullKey(item.list, item.fullKey);
            } else {
              item.fullKey = `${parentKey ? `${parentKey}.${item.key}` : item.key}`;
            }
          });
        };
        // 生成fullKey
        generateFullKey(newList);
        // 创建输出列表
        const result = [];
        const tableRowTemplate = {};
        // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key
        const generateFlatList = (list) => {
          list.forEach(item => {
            if (item.group && item.list) {
              generateFlatList(item.list);
            } else if (!item.group && !item.list) {
              result.push({ ...item, keyPath: item.fullKey.split('.') });
              set(tableRowTemplate, item.fullKey, undefined);
              tableRowTemplate.$editable = true;
            }
          });
        };
        generateFlatList(newList);
        this.tableList = result;
        this.tableRowTemplate = tableRowTemplate;
      },
      immediate: true
    }
  },
  methods: {
    $_get: get,
    // 处理新增逻辑
    handleNew() {
      const tableData = cloneDeep(this.tableData);
      tableData.push({ ...this.tableRowTemplate, $new: true });
      this.tableEditCell = {};
      this.tableData = tableData.map((o, i) => ({ ...o, $index: i }));
    },
    // 处理编辑逻辑
    handleEdit() {
      const tableData = cloneDeep(this.tableData);
      const selectionIndexArr = this.tableSelection.map(i => i.$index);
      tableData.forEach((r, i) => {
        if (selectionIndexArr.includes(r.$index)) {
          tableData[i].$editable = true;
          tableData[i].$edit = true;
          tableData[i].$new = true;
        }
      });
      this.tableEditCell = {};
      this.tableData = tableData.map((o, i) => ({ ...o, $index: i }));
    },
    // 处理保存逻辑
    handleSave() {
      const tableData = cloneDeep(this.tableData);
      tableData.forEach((r, i) => {
        delete tableData[i].$editable;
      });
      this.tableEditCell = {};
      this.tableData = tableData.map((o, i) => ({ ...o, $index: i }));
      this.$nextTick(() => {
        this.emitTableData(this.tableData);
        this.$emit(this.rowEdit ? 'row-edit' : 'row-new', this.tableData.filter(d => d.$new).map(d => {
          delete d.$new;
          return d;
        }));
      });
    },
    // 处理取消逻辑
    handleCancel() {
      let tableData = cloneDeep(this.tableData);
      tableData = tableData.filter(row => !row.$new);
      this.emitTableData(tableData);
    },
    // 处理删除逻辑
    handleDelete() {
      const tableData = cloneDeep(this.tableData);
      const selectionIndexArr = this.tableSelection.map(i => i.$index);
      if (!this.rowNew && !this.rowEdit) {
        this.$emit('row-delete', this.tableSelection);
      }
      this.tableEditCell = {};
      this.tableData = tableData.filter((d, i) => !selectionIndexArr.includes(i)).map((o, i) => ({ ...o, $index: i }));
    },
    // 更新表格数据
    emitTableData(tableData) {
      if (this.$listeners['input']) {
        this.$emit('input', tableData.map((o, i) => {
          return { ...o, $index: i, $editable: undefined, $new: undefined, $edit: undefined };
        }));
      } else {
        this.tableData = tableData;
      }
    },
    // 绑定表格列显示隐藏状态
    bindItemVisible(visible = true) {
      let result = visible;
      if (typeof visible === 'function') {
        result = visible(this.tableData);
      }
      return result;
    },
    // 双击单元格
    onCellDblclick(row, column, cell, event) {
      if (this.editable && !this.rowNew) {
        this.tableEditCell = { index: row.$index, key: column.property };
      }
    },
    // 编辑表格更新值
    onCellUpdate({ oldValue, value, row, key, fullKey }) {
      this.setCellValue({ value, row, key, fullKey });
    },
    // 编辑表格确认
    onCellUpdateDone({ oldValue, value, row, key, fullKey }) {
      this.tableEditCell = {};
      if (this.$listeners['cell-edit']) {
        const { tableData } = this.setCellValue({ value, row, key, fullKey });
        this.emitTableData(tableData);
        this.$emit('cell-edit', { row, key, fullKey, value });
      }
    },
    // 表格取消编辑
    onCellUpdateCancel({ oldValue, value, row, key, fullKey }) {
      if (row.$new !== true) {
        this.setCellValue({ value: oldValue, row, key, fullKey });
      }
    },
    // 设置表格值
    setCellValue({ value, row, key, fullKey }) {
      const tableData = cloneDeep(this.tableData);
      const tableRow = tableData[row.$index];
      set(tableRow, fullKey, value);
      tableData[row.$index] = tableRow;
      this.$set(this.tableData, row.$index, tableRow);
      return { tableData, tableRow };
    },
    // 表格选中
    onSelectionChange(selection) {
      this.tableSelection = selection;
      this.$emit('selection', selection);
    }
  }
};
</script>