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

// 配置是否必填
function isItemRequired(context, config) {
  // 渲染函数配置
  const contentProps = context.props || {};
  // 编辑器统一配置
  const editorProps = contentProps.editor || {};
  const editorRules = editorProps.rules || {};
  if (editorProps.validate !== true) {
    return false;
  }
  // 判断校验规则
  let rules = editorRules[config.prop] || [];
  const match = editorProps.items.find(i => i.prop === config.prop);
  if (match && match.rules) {
    let matchRules = match.rules;
    if (typeof match.rules === 'function') {
      matchRules = match.rules({});
    }
    rules = matchRules;
  }
  if (rules.length === 0) {
    return false;
  }
  let result = false;
  for (let rule of 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, value, scope, children) {
  // 渲染函数配置
  const contentProps = context.props || {};
  // 编辑器统一配置
  const editorProps = get(contentProps, 'editor') || {};
  const editorRules = editorProps.rules || {};
  let formItemProp = [editorProps.path].filter(i => i);
  formItemProp.push(scope.$index);
  formItemProp.push(item.prop);
  formItemProp = formItemProp.join('.');
  // 处理校验规则
  let itemRules = item.rules;
  if (typeof item.rules === 'function') {
    itemRules = item.rules({ item, value, index: scope.$index, ...scope });
  }
  return h(
    editorProps.formItem || 'el-form-item',
    {
      props: { prop: formItemProp, rules: itemRules || editorRules[item.prop], inlineMessage: 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);
    if (item.if === false) {
      return cellRender(h, context, item)(scope);
    }
    if (item.if && typeof item.if === 'function') {
      const showEditor = item.if({ item, value, index: scope.$index, ...scope });
      if (!showEditor) {
        return cellRender(h, context, item)(scope);
      }
    }
    let vnode = {};
    // 默认
    const setValue = val => {
      if (get(contentProps, 'editor.force') === true) {
        if (vnode.componentInstance) vnode.componentInstance.$set(scope.row, item.prop, val);
      } else {
        scope.row[item.prop] = val;
      }
    };
    const inputEvent = val => {
      if (get(contentProps, 'editor.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);
          if (vnode.componentInstance) vnode.componentInstance.$set(scope.row, bindProp, propValue[bindProp]);
        } else {
          setValue(val);
        }
      } else {
        setValue(val);
      }
    };
    // 向外提供的值
    const editorScope = { item, value, index: scope.$index, ...scope, onInput: inputEvent };
    // 编辑表单项配置
    let itemProps = item.props || {};
    if (typeof item.props === 'function') {
      itemProps = item.props(editorScope);
    }
    let itemAttrs = item.attrs || {};
    if (typeof item.attrs === 'function') {
      itemAttrs = item.attrs(editorScope);
    }
    let itemOn = item.on || {};
    if (typeof item.on === 'function') {
      itemOn = item.on(editorScope);
    }
    if (itemOn.input) {
      const itemOnInput = itemOn.input;
      itemOn.input = function(e) {
        inputEvent(e);
        itemOnInput(e);
      };
    } else {
      itemOn.input = inputEvent;
    }
    // 编辑器统一配置
    const editorProps = get(contentProps, 'editor') || {};
    // 生成虚拟节点
    vnode = h(item.is, {
      attrs: itemAttrs,
      props: { ...editorProps, ...itemProps, value },
      on: itemOn,
    });
    // 自定义具名插槽
    if (editorSlot) {
      return editorSlot(editorScope);
    }
    // 需要校验时外层嵌套校验组件
    if (editorProps.validate) {
      return formItemRender(h, context, item, value, 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;
    }
    if (isEditor) {
      if (props.className) {
        props.className = [props.className, 'column-editor'].filter(i => i).join(' ');
      } else {
        props.className = 'column-editor';
      }
    }
    // 处理插槽
    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) {
    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;
    // 兼容旧版value属性
    if (context.props.value && !context.props.data) {
      context.props.data = context.props.value;
      delete context.props.value;
    }
    return h('el-table', renderContext(context), [prependSlot, leftSlot, ...elTableColumns, rightSlot, appendSlot]);
  },
};
</script>

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