基于elementPlus的ElRow仿写ElSpace组件

先来代码

<script lang="ts">
  import { Fragment, Comment, defineComponent, h, isVNode, renderSlot, PropType } from 'vue';
  import { ElRow, ElCol } from 'element-plus';

  export default defineComponent({
    name: 'LayoutForm',
    props: {
      form: {
        type: Boolean,
        default: () => false,
      },
      table: {
        type: Boolean,
        default: () => false,
      },
      autoWidth: {
        type: Boolean,
        default: () => false,
      },
      grid: {
        type: [Number, String],
        default: () => '',
      },
      gutters: {
        type: [Number, String, Array] as PropType<number | string | (number | string)[]>,
        default: () => '',
      },
      rowClass: {
        type: String,
        default: () => '',
      },
    },
    setup(props, { slots }) {
      return () => {
        const extractChildren = (children: any, extractedChildren: any = []) => {
          children.forEach((child: any) => {
            if (child.type === Fragment) {
              if (Array.isArray(child.children)) {
                extractChildren(child.children, extractedChildren);
              }
            } else {
              if (!isVNode(child)) return;
              if (child.type === Comment) return;
              if (child.type === Fragment) return;
              const {
                span,
                offset,
                push,
                pull,
                xs,
                sm,
                md,
                lg,
                xl,
                tag,
                colBind = {},
                colMinWidth = '',
                'col-min-width': _colMinWidth = '',
                colClass = '',
                colStyle = '',
                'col-class': _colClass = '',
                'col-style': _colStyle = '',
                ...childProps
              } = child.props || {};
              let minWidthStyle = '';
              if (colMinWidth || _colMinWidth) {
                let [colminwidth, minProportion] = (colMinWidth || _colMinWidth).toString().split(',');
                minWidthStyle = `flex:${colminwidth};`;
                if (parseFloat(colminwidth).toString() === colminwidth) {
                  colminwidth += 'px';
                }
                minProportion = minProportion || colminwidth;
                minWidthStyle += `min-width:min(max(${colminwidth},${minProportion}),99%);`;
              }
              extractedChildren.push(
                h(
                  ElCol,
                  {
                    span,
                    offset,
                    push,
                    pull,
                    xs,
                    sm,
                    md,
                    lg,
                    xl,
                    tag,
                    key: child.props?.key || child.props?.prop,
                    ...colBind,
                    class: colClass || _colClass || colBind.class,
                    style: `${minWidthStyle};${colStyle || _colStyle || colBind.style}`,
                  },
                  { default: () => ({ ...child, props: childProps }) }
                )
              );
            }
          });
          return extractedChildren;
        };
        const children = renderSlot(slots, 'default', { key: 0 }, () => []);
        if (Array.isArray(children.children)) {
          const extractedChildren = extractChildren(children.children);
          return h(
            'div',
            {
              class: `el_row_gutter
                      ${props.form ? 'form' : ''}
                      ${props.table ? 'table' : ''}
                      ${props.autoWidth ? 'autoWidth' : ''}
                      ${props.grid ? 'grid' : ''}
                      ${props.gutters ? 'gutters' : ''}`,
            },
            h(
              ElRow,
              {
                class: `w_100 ${props.rowClass}`,
              },
              { default: () => extractedChildren }
            )
          );
        }
        return children.children;
      };
    },
    computed: {
      gridMinWidth() {
        let minWidth: any = this.grid;
        if (parseFloat(minWidth).toString() === minWidth.toString()) {
          minWidth = `${minWidth}px`;
        }
        return minWidth;
      },
      getters_x() {
        let gutter: any = this.gutters;
        if (typeof gutter === 'string') {
          gutter = gutter.split(',').map((v) => v.trim());
        }
        if (Array.isArray(gutter)) {
          gutter = gutter[1] === undefined ? gutter[0] : gutter[1];
        }
        if (!gutter && gutter !== 0) {
          gutter = 'calc(var(--const-interval) / 2)';
        }
        if (parseFloat(gutter).toString() === gutter.toString()) {
          gutter = `${gutter}px`;
        }
        return gutter;
      },
      getters_y() {
        let gutter: any = this.gutters;
        if (typeof gutter === 'string') {
          gutter = gutter.split(',').map((v) => v.trim());
        }
        if (Array.isArray(gutter)) {
          gutter = gutter[0];
        }
        if (!gutter && gutter !== 0) {
          gutter = 'calc(var(--const-interval) / 2)';
        }
        if (parseFloat(gutter).toString() === gutter.toString()) {
          gutter = `${gutter}px`;
        }
        return gutter;
      },
    },
  });
</script>

<style scoped>
  /* 基础属性样式 */
  .el_row_gutter {
    margin-left: calc(-1 * var(--el_row_gutter_x));
    margin-right: calc(-1 * var(--el_row_gutter_x));
    margin-top: calc(-1 * var(--el_row_gutter_y));
    margin-bottom: calc(-1 * var(--el_row_gutter_y));
  }
  .el_row_gutter > .el-row > .el-col {
    padding-left: var(--el_row_gutter_x);
    padding-right: var(--el_row_gutter_x);
    padding-top: var(--el_row_gutter_y);
    padding-bottom: var(--el_row_gutter_y);
  }
  .el_row_gutter > .el-row > .el-col > :deep(.el-form-item) {
    margin-bottom: unset;
  }

  .el_row_gutter > .el-row > .el-col > :deep(.el-form-item > .el-form-item__label) {
    height: unset;
    line-height: unset;
    align-items: center;
  }

  .el_row_gutter > .el-row > .el-col > :deep(.el-form-item > .el-form-item__content) {
    line-height: unset;
    overflow-wrap: anywhere;
  }

  .el_row_gutter > .el-row > .el-col > :deep(.el-form-item > .el-form-item__content > *:not(.el_row_gutter)) {
    width: 100%;
  }

  /* form属性样式 */
  .el_row_gutter.form {
    --el_row_gutter_x: 10px;
    --el_row_gutter_y: 10px;
  }

  .el_row_gutter.form > .el-row > .el-col > :deep(.el-form-item > .el-form-item__label),
  .el_row_gutter.form > .el-row > .el-col > :deep(.el-form-item > .el-form-item__label-wrap > .el-form-item__label) {
    align-items: center;
    padding-right: 25px;
  }

  /* table属性样式 */
  .el_row_gutter.table {
    --el_row_gutter_x: 0;
    --el_row_gutter_y: 0;
    position: relative;
  }

  .el_row_gutter.table::before {
    content: '';
    border-right: 1px solid var(--el-border-color);
    position: absolute;
    height: 100%;
    right: -1px;
  }

  .el_row_gutter.table::after {
    content: '';
    border-bottom: 1px solid var(--el-border-color);
    position: absolute;
    width: 100%;
    bottom: -1px;
  }

  .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item) {
    border-top: 1px solid var(--el-border-color);
    border-left: 1px solid var(--el-border-color);
    position: relative;
    min-height: 40px;
    height: 100%;
  }

  /* .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item)::before {
    content: '';
    height: 100%;
    right: -1px;
    position: absolute;
    border-right: 1px solid var(--el-border-color);
  } */

  .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item)::after {
    content: '';
    width: 100%;
    bottom: -1px;
    position: absolute;
    border-bottom: 1px solid var(--el-border-color);
  }

  .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item > .el-form-item__label) {
    align-items: center;
    padding-right: unset;
    justify-content: center;
    background-color: #f2f3f5;
    position: relative;
  }

  .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item > .el-form-item__label)::after {
    content: '';
    height: 100%;
    right: 0;
    position: absolute;
    border-right: 1px solid var(--el-border-color);
  }

  .el_row_gutter.table > .el-row > .el-col > :deep(.el-form-item > .el-form-item__content) {
    padding: 5px 5px;
    border-left: none;
  }

  /* autoWidth属性样式 */
  .el_row_gutter.autoWidth {
    --el_row_gutter_x: 3px;
    --el_row_gutter_y: 3px;
  }

  .el_row_gutter.autoWidth > .el-row > .el-col.el-col-24 {
    flex: unset;
  }

  /* grid属性样式 */
  .el_row_gutter.grid > .el-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(v-bind(gridMinWidth), 100%), 1fr));
  }

  /* gutters属性样式 */
  .el_row_gutter.gutters {
    --el_row_gutter_x: v-bind(getters_x);
    --el_row_gutter_y: v-bind(getters_y);
  }
</style>

初衷

ElSpace组件简单的包裹后添加margin属性,在一些响应式布局中会在右侧留有白边影响整体

解决思路

使用css margin负数的特性让中间盒子大出一圈供子盒子放置多出来的边边,使用ElRow包裹后内容一方面要有一个中间盒子包裹,又要在每个子元素上包裹正好合适,还同时具备了栅格布局的功能

场景1




对比ElSpace该组件可以使内容左右与父盒子都无间隙,布局更加自然(要加autoWidth属性哦)

场景2





使用col-min-width属性可以设置布局容器的最小宽度,在页面缩小时先将外层进行挤压,保证页面宽度大于该值时页面中的元素不会被过度压缩,也可用于元素分组布局等场景

场景3





仅仅是宽度同值怎么能显示出组件的厉害呢,col-min-width可传递两个参数,第一是在父盒子中的占比,第二个是最小宽度,第二个值为空则取第一个值(见场景2),实现右侧盒子固定宽度,在页面缩小至一定值后再混入所有盒子中

场景4




grid布局当然也得有啊,grid参数接收一个值用来设置子元素最小宽度,剩下的就交给grid去划分每行最多放几个,每个有多大,当然通过colStyle或colClass可随意调整(见页面跨两列的选项)但是在页面缩小至不足两列时会有bug,希望大家帮忙提提建议

场景5




既然有栅格布局了,模拟个表格也不过分吧,设置table属性,将会把ElCol样式调整为表格样式,当然也支持嵌套循环等操作

暂时也就开发出了这么多用法,相信随着项目开发需求的变态,组件的未来会有更多功能

posted @ 2023-09-25 16:54  杜柯枫  阅读(132)  评论(0编辑  收藏  举报