my-flex/src/Flex.vue

<template>
  <div :class="classes">
    <slot></slot>
  </div>
</template>

<script>
  /**
   * Flex布局
   * @module $ui/components/my-flex
   */
  import {isFlexSupported} from '$ui/utils/styleChecker'
  import responsive, {responsiveArray} from '$ui/utils/responsive'

  // 响应式默认配置
  const defaultColumnMap = {
    xxl: 3,
    xl: 3,
    lg: 3,
    md: 3,
    sm: 2,
    xs: 2
  };

  /**
   * 插槽
   * @member slots
   * @property {string} default 默认插槽,定义子组件
   */
  export default {
    name: 'MyFlex',
    provide() {
      return {
        flex: this
      }
    },
    /**
     * 属性参数
     * @member props
     * @property {boolean} [inline] 是否内行元素
     * @property {string} [direction=row] 主轴的方向(即项目的排列方向), 可选值:'row', 'row-reverse', 'column', 'column-reverse'
     * @property {string} [wrap=nowrap] 一条轴线排不下,如何换行, 可选值:'nowrap', 'wrap', 'wrap-reverse'
     * @property {string} [justify=flex-start] 项目在主轴上的对齐方式, 可选值:'flex-start', 'flex-end', 'center', 'space-between', 'space-around'
     * @property {string} [alignItems=stretch] 项目在交叉轴上如何对齐, 可选值:'flex-start', 'flex-end', 'center', 'baseline', 'stretch'
     * @property {string} [alignContent=stretch] 多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用, 可选值:'flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'stretch'
     * @property {boolean} [border] 显示边框
     * @property {boolean} [fit] 适配父容器
     * @property {number|object} [column] 适配列数,支持响应配置{xxl,xl,lg,md,sm,xs}, direction为 row 或 row-reverse 时有效.
     */
    props: {
      // 是否内行元素
      inline: Boolean,
      // flex-direction属性决定主轴的方向(即项目的排列方向)
      direction: {
        type: String,
        default: 'row',
        validator(val) {
          // row(默认值):主轴为水平方向,起点在左端。
          // row-reverse:主轴为水平方向,起点在右端。
          // column:主轴为垂直方向,起点在上沿。
          // column-reverse:主轴为垂直方向,起点在下沿。
          return ['row', 'row-reverse', 'column', 'column-reverse'].includes(val)
        }
      },
      // 默认情况下,项目都排在一条线(又称"轴线")上。flex-wrap属性定义,如果一条轴线排不下,如何换行。
      wrap: {
        type: String,
        default: 'nowrap',
        validator(val) {
          // nowrap(默认):不换行。
          // wrap:换行,第一行在上方。
          // wrap-reverse:换行,第一行在下方。
          return ['nowrap', 'wrap', 'wrap-reverse'].includes(val)
        }
      },
      // 属性定义了项目在主轴上的对齐方式。
      justify: {
        type: String,
        default: 'flex-start',
        validator(val) {
          // flex-start(默认值):左对齐
          // flex-end:右对齐
          // center: 居中
          // space-between:两端对齐,项目之间的间隔都相等。
          // space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
          return ['flex-start', 'flex-end', 'center', 'space-between', 'space-around'].includes(val)
        }
      },
      // 属性定义项目在交叉轴上如何对齐。
      alignItems: {
        type: String,
        default: 'stretch',
        validator(val) {
          // flex-start:交叉轴的起点对齐。
          // flex-end:交叉轴的终点对齐。
          // center:交叉轴的中点对齐。
          // baseline: 项目的第一行文字的基线对齐。
          // stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
          return ['flex-start', 'flex-end', 'center', 'baseline', 'stretch'].includes(val)
        }
      },
      // 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
      alignContent: {
        type: String,
        default: 'stretch',
        validator(val) {
          // flex-start:与交叉轴的起点对齐。
          // flex-end:与交叉轴的终点对齐。
          // center:与交叉轴的中点对齐。
          // space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
          // space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
          // stretch(默认值):轴线占满整个交叉轴。
          return ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'stretch'].includes(val)
        }
      },
      // 显示边框
      border: {
        type: Boolean
      },
      // 适配父容器
      fit: Boolean,
      // 适配列数,支持响应配置, direction为 row 或 row-reverse 时有效
      column: [Number, Object]
    },
    data() {
      return {
        screens: {}
      }
    },
    computed: {
      classes() {
        return [
          'my-flex',
          `is-direction-${this.direction}`,
          `is-wrap-${this.wrap}`,
          `is-justify-${this.justify}`,
          `is-align-items-${this.alignItems}`,
          `is-align-content-${this.alignContent}`,
          {
            'is-inline': this.inline,
            'is-border': this.border,
            'is-fit': this.fit
          }
        ]
      }
    },
    methods: {
      getColumn() {
        if (typeof this.column === 'object') {
          for (let i = 0; i < responsiveArray.length; i++) {
            const breakpoint = responsiveArray[i]
            if (this.screens[breakpoint]) {
              return this.column[breakpoint] || defaultColumnMap[breakpoint]
            }
          }
        }
        if (typeof this.column === 'number') {
          return this.column
        }

        return null
      }
    },
    mounted() {
      this.token = responsive.on(screens => {
        if (typeof this.column !== 'object') {
          return
        }
        this.screens = screens
      })
    },
    created() {
      if (!isFlexSupported) {
        console.warn('浏览器不支持Flex布局')
      }
    },
    beforeDestroy() {
      responsive.off(this.token)
    }
  }
</script>