editor.vue 6.63 KB
<script>
import { get, set, cloneDeep } from '../utils';
import { renderContext, setDefaultContextClass } from '../utils/vnode';

// 配置是否必填
function isItemRequired(context, config) {
  // 渲染函数配置
  const contentProps = context.props || {};
  // 编辑器统一配置
  const editorConfig = contentProps.editor || {};
  let result = false;
  if (!editorConfig.items) {
    return false;
  }
  for (let item of editorConfig.items) {
    if (item.prop === config.prop && item.rules) {
      for (let rule of item.rules) {
        if (rule.required) {
          result = true;
          break;
        }
      }
    }
  }
  return result;
}

// 标题渲染
function headerRender(h, context, item) {
  // 表头插槽
  const headerSlot = context.scopedSlots[`header-${item.prop}`];
  const required = isItemRequired(context, item);
  return function(scope) {
    scope.required = required;
    // 自定义具名插槽
    if (headerSlot) {
      return headerSlot(scope);
    }
    // 自定义渲染函数
    if (item.header) {
      return item.header(item, h, scope);
    }
    // 如果是必填项
    if (required) {
      return h('span', { class: 'required' }, item.label);
    }
    // 默认取值
    return item.label;
  };
}

// 单元格渲染
function cellRender(h, context, item) {
  const cellSlot = context.scopedSlots[`cell-${item.prop}`];
  return function(scope) {
    const value = get(scope.row, item.prop);
    // 自定义具名插槽
    if (cellSlot) {
      return cellSlot({ item, value, index: scope.$index, ...scope });
    }
    // 自定义渲染函数
    if (item.render) {
      return item.render(value, scope.row, h, scope.$index);
    }
    // 默认取值
    return get(scope.row, item.prop);
  };
}

// 表单项渲染
function formItemRender(h, context, item, scope, children) {
  // 渲染函数配置
  const contentProps = context.props || {};
  // 编辑器统一配置
  const editorProps = get(contentProps, 'editor.props') || {};
  let formItemProp = [editorProps.path].filter(i => i);
  formItemProp.push(scope.$index);
  formItemProp.push(item.prop);
  formItemProp = formItemProp.join('.');
  return h(
    editorProps.formItem || 'el-form-item',
    {
      props: { prop: formItemProp, rules: item.rules, 'inline-message': true },
    },
    children,
  );
}

// 编辑器渲染
function editorRender(h, context, item) {
  const editorSlot = context.scopedSlots[`editor-${item.prop}`];
  const contentProps = context.props || {};
  return function(scope) {
    const value = get(scope.row, item.prop);
    let vnode = {};
    // 默认
    const inputEvent = val => {
      if (get(contentProps, 'editor.props.deep') === true) {
        if (item.prop.indexOf('.') > -1 || item.prop.indexOf('[') > -1) {
          let separator = '';
          if (item.prop.indexOf('.') > -1) {
            separator = '.';
          } else if (item.prop.indexOf('[') > -1) {
            separator = '[';
          }
          const path = item.prop.split(separator);
          const bindProp = path[0];
          const propValue = cloneDeep(scope.row);
          set(propValue, item.prop, val);
          vnode.componentInstance.$set(scope.row, bindProp, propValue[bindProp]);
        } else {
          // set(scope.row, item.prop, val);
          scope.row[item.prop] = val;
        }
      } else {
        scope.row[item.prop] = val;
        // set(contentProps.data, `[${[scope.$index]}]${item.prop}`, val);
      }
      if (item.on && item.on.input) {
        item.on.input(val);
      }
    };
    const blurEvent = val => {
      if (item.on && item.on.blur) {
        item.on.blur(val);
      }
    };
    // 编辑表单项配置
    const itemProps = item.props || {};
    // 编辑器统一配置
    const editorProps = get(contentProps, 'editor.props') || {};
    // 生成虚拟节点
    vnode = h(item.is, {
      attrs: item.attrs,
      props: { ...editorProps, ...itemProps, value },
      on: { input: inputEvent, blur: blurEvent },
    });
    // 自定义具名插槽
    if (editorSlot) {
      return editorSlot({ item, value, index: scope.$index, ...scope, onInput: inputEvent });
    }
    // 需要校验时外层嵌套校验组件
    if (editorProps.validate) {
      return formItemRender(h, context, item, scope, [vnode]);
    }
    return vnode;
  };
}

// 跟进columns生成列
function createElTableColumns(h, context, columns) {
  const props = context.props || {};
  const editorConfig = props.editor || {};
  return columns.map((item, index) => {
    const { attrs, on, ...props } = item;
    const items = editorConfig.items || [];
    // 当前列编辑器配置
    let editorItem = items.find(i => i.prop === item.prop);
    // 当前列有编辑器配置或编辑器插槽的情况
    const isEditor = editorItem || context.scopedSlots[`editor-${item.prop}`];
    if (context.scopedSlots[`editor-${item.prop}`] && !editorItem) {
      editorItem = item;
    }
    // 处理插槽
    const scopedSlots = {
      header: headerRender(h, context, item),
      default: isEditor ? editorRender(h, context, editorItem) : cellRender(h, context, item),
    };
    return h('el-table-column', { key: index, attrs, props, on, scopedSlots });
  });
}

export default {
  name: 'TableEditor',
  functional: true,
  render(h, context) {
    console.log(context);
    const props = context.props || {};
    // 设置默认class名称,用来追加一些默认的样式
    let scopedSlots = context.scopedSlots || {};
    setDefaultContextClass(context, 'z-table-editor');
    // 如有默认插槽则相当于直接写el-table
    if (scopedSlots.default) {
      return h('el-table', context);
    }
    const columns = props.columns || [];
    // 通过columns快速生成el-table-column
    const elTableColumns = createElTableColumns(h, context, columns);
    // 前置插槽
    const prependSlot = scopedSlots.prepend ? scopedSlots.prepend() : '';
    // 左侧插槽
    const leftSlot = scopedSlots.left ? scopedSlots.left() : '';
    // 右侧插槽
    const rightSlot = scopedSlots.right ? scopedSlots.right() : '';
    // 后置插槽
    const appendSlot = scopedSlots.append ? scopedSlots.append() : '';
    // 渲染组件时移除当前组件特有的props,避免透传不必要的参数
    delete context.columns;
    return h('el-table', renderContext(context), [prependSlot, leftSlot, ...elTableColumns, rightSlot, appendSlot]);
  },
};
</script>

<style lang="scss">
.z-table-editor {
  &.el-table td .cell {
    padding-top: 2px;
    padding-bottom: 2px;
    padding-right: 2px;
  }
  &.el-table td .cell .el-form-item {
    margin-bottom: 0;
  }
  &.el-table th.el-table__cell .required::before {
    content: '*';
    color: red;
    margin-right: 4px;
  }
}
</style>