index.vue 7.8 KB
<style>
.eagle-table {
  width: 100%;
}
.eagle-table-cell-edit {
  display: flex;
  align-items: center;
}
</style>

<template>
  <div @keyup.enter="editCell = {}">
    <pre>{{ tableList }}</pre>
    <el-table class="eagle-table" ref="table" :data="tableData" v-bind="{ size: 'small', ...tableProps }" v-on="tableEvents" @cell-dblclick="onCellDblclick">
      <!-- 默认表格插槽 -->
      <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('-')] || $slots[item.keyPath.join('-')]" :name="item.keyPath.join('-')" v-bind="item"></slot>
            <!-- 如果有表格列值渲染具名插槽 -->
            <el-table-column v-else-if="$scopedSlots[`value-${item.keyPath.join('-')}`] || $slots[`value-${item.keyPath.join('-')}`]"
              v-bind="item" :prop="item.agentKey || item.fullKey || item.key" :key="index"
              :min-width="item.minWidth || item['min-width']"
            >
              <template slot-scope="{ row, column, $index }">
                <div v-if="editCell.index === row.$index && editCell.key === (item.agentKey || item.fullKey || item.key)" class="eagle-table-cell-edit">
                  <component
                    :value="$_get(row, item.fullKey)"
                    :is="item.type" v-bind="item.props" :style="item.style" size="mini"
                    @input="v => onCellUpdate({ value: v, row, key: item.fullKey })"
                  ></component>
                  <el-button type="text" icon="el-icon-check" @click="editCell = {}"></el-button>
                </div>
                <slot v-else :name="`value-${item.keyPath.join('-')}`" v-bind="item"
                  :row="row" :value="$_get(row, item.fullKey)" :column="column" :index="$index"
                ></slot>
              </template>
            </el-table-column>
            <!-- 如果表格列配置了值渲染参数 -->
            <el-table-column v-else-if="item.render" v-bind="item" :prop="item.agentKey || item.fullKey || item.key"
              :key="index" :min-width="item.minWidth || item['min-width']"
            >
              <template slot-scope="{ row, column, $index }">
                <div v-if="editCell.index === row.$index && editCell.key === (item.agentKey || item.fullKey || item.key)" class="eagle-table-cell-edit">
                  <component
                    :value="$_get(row, item.fullKey)"
                    :is="item.type" v-bind="item.props" :style="item.style" size="mini"
                    @input="v => onCellUpdate({ value: v, row, key: item.fullKey })"
                  ></component>
                  <el-button type="text" icon="el-icon-check" @click="editCell = {}"></el-button>
                </div>
                <template v-else>
                  <component :is="item.render.type" v-bind="item.render.props" :style="item.render.style">
                    <template v-if="item.render.children">{{ bindItemRenderChildren(item, { row, column, $index }) }}</template>
                    <template v-else>{{ $_get(row, item.fullKey) }}</template>
                  </component>
                </template>
              </template>
            </el-table-column>
            <!-- 默认表格列渲染 -->
            <el-table-column v-else v-bind="item" :prop="item.agentKey || item.fullKey || item.key"
              :key="index" :min-width="item.minWidth || item['min-width'] || 120"
            >
              <template slot-scope="{ row }">
                <div v-if="editCell.index === row.$index && editCell.key === (item.agentKey || item.fullKey || item.key)" class="eagle-table-cell-edit">
                  <component
                    :value="$_get(row, item.fullKey)"
                    :is="item.type" v-bind="item.props" :style="item.style" size="mini"
                    @input="v => onCellUpdate({ value: v, row, key: item.fullKey })"
                  ></component>
                  <el-button type="text" icon="el-icon-check" @click="editCell = {}"></el-button>
                </div>
                <template v-else>{{ $_get(row, item.fullKey) }}</template>
              </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';

export default {
  name: 'TableNew',
  props: {
    // 用于实例化本组件绑定v-model的值
    value: Array,
    // 配置列表
    list: {
      type: Array,
      required: true
    },
    // 表格参数
    tableProps: {
      type: Object,
      default() { return {} }
    },
    // 表格事件
    tableEvents: Object,
  },
  data() {
    return {
      tableList: [],
      tableData: [],
      editCell: {},
    };
  },
  computed: {
    // 表格实体
    instance: {
      get() {
        return this.$refs.table;
      }
    }
  },
  watch: {
    value: {
      handler(val = []) {
        this.tableData = val.map((o, i) => ({ ...o, $index: i }));
      },
      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 = [];
        // 生成列表值的全路径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('.') });
            }
          });
        };
        generateFlatList(newList);
        this.tableList = result;
      },
      immediate: true
    }
  },
  methods: {
    $_get: get,
    // 绑定表格列显示隐藏状态
    bindItemVisible(visible = true) {
      let result = visible;
      if (typeof visible === 'function') {
        result = visible(this.tableData);
      }
      return result;
    },
    // 处理表格项渲染时的子值
    bindItemRenderChildren(item, scope) {
      return item.render.children instanceof Function ? item.render.children(scope) : this.$_get(scope.row, item.fullKey)
    },
    // 双击单元格
    onCellDblclick(row, column, cell, event) {
      this.editCell = { index: row.$index, key: column.property };
    },
    // 编辑表格更新值
    onCellUpdate({ value, row, key }) {
      const tableData = cloneDeep(this.tableData);
      const tableRow = tableData[row.$index];
      set(tableRow, key, value);
      tableData[row.$index] = tableRow;
      this.$set(this.tableData, row.$index, tableRow);
      // this.tableData = tableData;
      // this.$emit('input', tableData);
    }
  }
};
</script>