Vue (grid)网格组件

解析vant-ui的 grid 源码修改的。导入即可使用。无需任何依赖。api和使用方法同vant-ui官方地址。

Grid API: grid

  • grid-item 的slot简单的写了一下,主要是研究源码和实现方式。

效果图

使用

    <grid clickable>
      <grid-item icon="el-icon-phone-outline" text="文字" to="/system/menuManagement" />
      <grid-item icon="el-icon-eleme" text="文字" />
      <grid-item icon="el-icon-upload" text="文字" />
      <grid-item><div>文字</div></grid-item>
    </grid>
Grid
<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2022-07-05 
 -->


<template>
  <div :class="[bem(), { 'hairline--top': border && !gutter } ]">
    <slot />
  </div>
</template>

<script>
import { ParentMixin } from "./mixins";
const isDef = (val) => val !== undefined && val !== null
const isNumeric = (val) => /^\d+(\.\d+)?$/.test(val)
function addUnit(value) {
  if (!isDef(value)) return undefined;

  value = String(value);
  return isNumeric(value) ? value + "px" : value;
}
export default {
  name: 'grid',
  mixins: [ParentMixin('grid')],
  props: {
    square: Boolean,
    gutter: [Number, String],
    iconSize: [Number, String],
    direction: { type: String, default: 'vertical' },
    clickable: Boolean,
    columnNum: { type: [Number, String], default: 4 },
    center: { type: Boolean, default: true },
    border: { type: Boolean, default: true }
  },

  methods: {
    createBem(name) {
      /**
       * bem helper
       * b() // 'button'
       * b('text') // 'button__text'
       * b({ disabled }) // 'button button--disabled'
       * b('text', { disabled }) // 'button__text button__text--disabled'
       * b(['disabled', 'primary']) // 'button button--disabled button--primary'
       */
      return function (el, mods) {
        function gen(name, mods) {
          if (!mods) return '';
          if (typeof mods === 'string') return " " + name + "--" + mods;
          if (Array.isArray(mods)) return mods.reduce((ret, item) => ret + gen(name, item), '');
          return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? gen(name, key) : ''), '');
        }

        if (el && typeof el !== 'string') {
          mods = el; el = '';
        }

        el = el ? name + "__" + el : name;
        return "" + el + gen(el, mods);
      };
    }
  },
  computed: {
    style() {
      const { gutter } = this;
      if (!gutter) return;
      return { paddingLeft: addUnit(gutter) };
    },
    bem() {
      return (...cls) => this.createBem(this.$options.name)(...cls);
    },
  },
  //  End
}

</script>

<style lang='css' scoped>
.hairline--top {
  position: relative;
}
.grid {
  display: -webkit-box;
  display: -webkit-flex;
  display: flex;
  -webkit-flex-wrap: wrap;
  flex-wrap: wrap;
}
</style>

Grid-Item
<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2022-07-05 
 -->


<template>
  <div :class="bem({square: parent.square})" :style="style">
    <div :class="[bem('content', [parent.direction, {center: parent.center, square: parent.square, clickable: parent.clickable, curround: parent.border && parent.gutter}]), { 'hairline': parent.border && !parent.gutter }]" :style="contentStyle" :role="parent.clickable ? 0 : null"
      :tabindex="parent.clickable ? 0 : null" @click="onClick">
      <slot>
        <i :class="[bem('icon'), icon]" />
        <span :class="bem('text')">{{text}}</span>
      </slot>
    </div>
  </div>
</template>

<script>
import { ChildrenMixin } from "./mixins";
const isDef = (val) => val !== undefined && val !== null
const isNumeric = (val) => /^\d+(\.\d+)?$/.test(val)
function addUnit(value) {
  if (!isDef(value)) return undefined;
  value = String(value);
  return isNumeric(value) ? value + "px" : value;
}
function isRedundantNavigation(err) {
  return err.name === 'NavigationDuplicated' || // compatible with vue-router@3.3
    err.message && err.message.indexOf('redundant navigation') !== -1;
}
function route(router, config) {
  var to = config.to,
    url = config.url,
    replace = config.replace;
  if (to && router) {
    var promise = router[replace ? 'replace' : 'push'](to);
    /* istanbul ignore else */

    if (promise && promise.catch) {
      promise.catch(err => {
        if (err && !isRedundantNavigation(err)) {
          throw err;
        }
      });
    }
  } else if (url) {
    replace ? location.replace(url) : location.href = url;
  }
}
export default {
  name: 'grid-item',
  mixins: [ChildrenMixin('grid')],
  props: {
    url: String,
    replace: Boolean,
    to: [String, Object],
    dot: Boolean,
    text: String,
    icon: String,
    iconPrefix: String,
    badge: [Number, String],
    // @deprecated
    info: [Number, String]
  },
  mounted() {
  },
  methods: {
    onClick: function onClick(event) {
      this.$emit('click', event);
      route(this.$router, this);
    },
    createBem(name) {
      /**
       * bem helper
       * b() // 'button'
       * b('text') // 'button__text'
       * b({ disabled }) // 'button button--disabled'
       * b('text', { disabled }) // 'button__text button__text--disabled'
       * b(['disabled', 'primary']) // 'button button--disabled button--primary'
       */
      return function (el, mods) {
        function gen(name, mods) {
          if (!mods) return '';
          if (typeof mods === 'string') return " " + name + "--" + mods;
          if (Array.isArray(mods)) return mods.reduce((ret, item) => ret + gen(name, item), '');
          return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? gen(name, key) : ''), '');
        }

        if (el && typeof el !== 'string') {
          mods = el; el = '';
        }

        el = el ? name + "__" + el : name;
        return "" + el + gen(el, mods);
      };
    }
  },
  computed: {
    bem() {
      return (...cls) => this.createBem(this.$options.name)(...cls);
    },
    style() {
      var _this$parent = this.parent;
      var square = _this$parent.square;
      var gutter = _this$parent.gutter;
      var columnNum = _this$parent.columnNum;
      var percent = 100 / columnNum + "%";
      var style = { flexBasis: percent };

      if (square) {
        style.paddingTop = percent;
      } else if (gutter) {
        var gutterValue = addUnit(gutter);
        style.paddingRight = gutterValue;

        if (this.index >= columnNum) {
          style.marginTop = gutterValue;
        }
      }

      return style;
    },
    contentStyle() {
      var _this$parent2 = this.parent,
        square = _this$parent2.square,
        gutter = _this$parent2.gutter;

      if (square && gutter) {
        var gutterValue = addUnit(gutter);
        return { right: gutterValue, bottom: gutterValue, height: 'auto' };
      }
      return {};
    }

  },
  //  End
}

</script>

<style lang='css' scoped>
.grid-item {
  position: relative;
  box-sizing: border-box;
}
.grid-item--square {
  height: 0;
}
.grid-item__icon {
  font-size: 28px;
}
.grid-item__icon-wrapper {
  position: relative;
}
.grid-item__text {
  color: #646566;
  font-size: 12px;
  line-height: 1.5;
  word-break: break-all;
}
.grid-item__icon + .grid-item__text {
  margin-top: 8px;
}
.grid-item__content {
  display: -webkit-box;
  display: -webkit-flex;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -webkit-flex-direction: column;
  flex-direction: column;
  box-sizing: border-box;
  height: 100%;
  padding: 16px 8px;
  background-color: #fff;
}
.grid-item__content::after {
  z-index: 1;
  border-width: 0 1px 1px 0;
}
.grid-item__content--square {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
}
.grid-item__content--center {
  -webkit-box-align: center;
  -webkit-align-items: center;
  align-items: center;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
  justify-content: center;
}
.grid-item__content--horizontal {
  -webkit-box-orient: horizontal;
  -webkit-box-direction: normal;
  -webkit-flex-direction: row;
  flex-direction: row;
}
.grid-item__content--horizontal .grid-item__icon + .grid-item__text {
  margin-top: 0;
  margin-left: 8px;
}
.grid-item__content--surround::after {
  border-width: 1px;
}
.grid-item__content--clickable {
  cursor: pointer;
}
.grid-item__content--clickable:active {
  background-color: #f2f3f5;
}
</style>

mixins
此部分代码未进行优化
export function ChildrenMixin(_parent, options) {
  var _inject, _computed;

  if (options === void 0) {
    options = {};
  }

  var indexKey = options.indexKey || 'index';
  return {
    inject: (_inject = {}, _inject[_parent] = {
      default: null
    }, _inject),
    computed: (_computed = {
      parent: function parent() {
        if (this.disableBindRelation) {
          return null;
        }

        return this[_parent];
      }
    }, _computed[indexKey] = function () {
      this.bindRelation();

      if (this.parent) {
        return this.parent.children.indexOf(this);
      }

      return null;
    }, _computed),
    watch: {
      disableBindRelation: function disableBindRelation(val) {
        if (!val) {
          this.bindRelation();
        }
      }
    },
    mounted: function mounted() {
      this.bindRelation();
    },
    beforeDestroy: function beforeDestroy() {
      var _this = this;

      if (this.parent) {
        this.parent.children = this.parent.children.filter(function (item) {
          return item !== _this;
        });
      }
    },
    methods: {
      bindRelation: function bindRelation() {
        if (!this.parent || this.parent.children.indexOf(this) !== -1) {
          return;
        }

        var children = [].concat(this.parent.children, [this]);
        sortChildren(children, this.parent);
        this.parent.children = children;
      }
    }
  };
}

export function ParentMixin(parent) {
  return {
    provide: function provide() {
      var _ref;

      return _ref = {}, _ref[parent] = this, _ref;
    },
    data: function data() {
      return {
        children: []
      };
    }
  };
}

function flattenVNodes(vnodes) {
  var result = [];

  function traverse(vnodes) {
    vnodes.forEach(function (vnode) {
      result.push(vnode);

      if (vnode.componentInstance) {
        traverse(vnode.componentInstance.$children.map(function (item) {
          return item.$vnode;
        }));
      }

      if (vnode.children) {
        traverse(vnode.children);
      }
    });
  }

  traverse(vnodes);
  return result;
} // sort children instances by vnodes order


export function sortChildren(children, parent) {
  var componentOptions = parent.$vnode.componentOptions;

  if (!componentOptions || !componentOptions.children) {
    return;
  }

  var vnodes = flattenVNodes(componentOptions.children);
  children.sort(function (a, b) {
    return vnodes.indexOf(a.$vnode) - vnodes.indexOf(b.$vnode);
  });
}

严谨转载

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值