my-wrapper/src/Wrapper.vue

<template>
  <div class="my-wrapper" :class="{'is-fit': fit,'is-split':split,'is-no-header':!header}">
    <div v-if="header" class="my-wrapper__header" :class="classes">
      <my-breadcrumb class="my-wrapper__breadcrumb"
                     v-if="breadcrumb && breadcrumb.length"
                     :data="breadcrumb"></my-breadcrumb>
      <my-float v-if="$slots.title || $slots.actions || title"
                class="my-wrapper__ft">
        <my-float-item float="left"
                       class="my-wrapper__title">
          <span v-if="back" @click="handleBack" class="my-wrapper__back"><i class="el-icon-back"></i></span>
          <slot name="title">{{title}}</slot>
        </my-float-item>
        <my-float-item float="left" class="my-wrapper__sub-title">
          <slot name="subTitle">{{subTitle}}</slot>
        </my-float-item>
        <my-float-item v-if="$slots.actions"
                       class="my-wrapper__actions"
                       float="right">
          <slot name="actions"></slot>
        </my-float-item>
      </my-float>

      <div v-if="$slots.extra || extra" class="my-wrapper__extra">
        <slot name="extra">{{extra}}</slot>
      </div>

      <div v-if="links && links.length" class="my-wrapper__links">
        <span v-for="(item, index) in links"
              :key="`link${index}`"
              class="my-wrapper__links-item"
              :class="{'is-active':item.value===activeLink}"
              @click="handleLinkClick(item)">
          <slot name="link" :link="item" :index="index">
               {{item.label}}
          </slot>
        </span>
      </div>
    </div>
    <div ref="body" class="my-wrapper__body">
      <slot></slot>
    </div>

    <div v-if="$slots.footer" class="my-wrapper__footer">
      <slot name="footer"></slot>
    </div>


  </div>
</template>

<script>
  /**
   * 页面容器组件
   * @module $ui/components/my-wrapper
   */
  import {MyBreadcrumb, MyFloat, MyFloatItem} from '$ui'
  import {on, off} from 'element-ui/lib/utils/dom'

  /**
   * 插槽
   * @member slots
   * @property {string} default 默认插槽,可以放置my-container
   * @property {string} title 定义标题内容
   * @property {string} subTitle 子标题内容
   * @property {string} actions 定义操作区
   * @property {string} extra 定义额外扩展内容
   * @property {string} link 作用域插槽,定义链接子项,参数:link 链接项数据,index:在links的索引
   * @property {string} footer 底部内容
   */

  export default {
    name: 'MyWrapper',
    components: {
      MyBreadcrumb,
      MyFloat,
      MyFloatItem
    },
    /**
     * 属性参数
     * @member props
     * @property {string} [title] 标题文本,复杂内容可用插槽定义
     * @property {string} [subTitle] 子标题文本, 复杂内容可用插槽定义
     * @property {string} [extra] 额外内容文本,复杂内容可用插槽定义
     * @property {Array} [breadcrumb] 面包屑数据配置,项包含字段:label, icon, to
     * @property {string} breadcrumb.label 文本
     * @property {string|object} [breadcrumb.icon] 图标配置
     * @property {string|object} [breadcrumb.to] 链接路由
     * @property {Array} [links] 链接按钮数据配置,项包含字段:label, value, to
     * @property {string} [links.label] 菜单文本
     * @property {string|number} [links.value] 值或标识
     * @property {string|object} [links.to] 链接路由
     * @property {string|number} [defaultActiveLink] 默认选中的链接值
     * @property {boolean} [header=true] 是否显示头部
     * @property {boolean} [split=true] 是否显示分隔
     * @property {boolean} [back=false] 显示返回按钮
     * @property {boolean} [fit=false] 是否自适应高度
     */
    props: {
      title: String,
      subTitle: String,
      extra: String,
      //  [{label, icon, to}]
      breadcrumb: Array,
      // [{label, value, to}]
      links: Array,
      defaultActiveLink: [String, Number],
      header: {
        type: Boolean,
        default: true
      },
      // 高度自适应
      fit: {
        type: Boolean,
        default: false
      },
      // 显示分隔
      split: {
        type: Boolean,
        default: true
      },
      // 返回按钮
      back: Boolean
    },
    data() {
      return {
        activeLink: this.defaultActiveLink,
        showHeaderLine: false
      }
    },
    computed: {
      classes() {
        return {
          'has-links': this.links && this.links.length > 0,
          'is-border-line': this.showHeaderLine
        }
      }
    },
    watch: {
      defaultActiveLink: {
        immediate: true,
        handler(val) {
          this.activeLink = val
        }
      }
    },
    methods: {
      handleLinkClick(item) {
        this.activeLink = item.value
        if (this.$router && item.to) {
          this.$router.push(item.to)
        }
        /**
         * 点击链接时触发
         * @event link-click
         * @param {Object} item 链接项数据对象
         */
        this.$emit('link-click', item)
      },
      handleBack() {
        if (this.$listeners.back) {
          /**
           * 点击返回按钮时触发
           * @event back
           */
          this.$emit('back')
        } else {
          this.$router && this.$router.back()
        }
      },
      handleScroll(e) {
        const scrollTop = e.target.scrollTop
        this.showHeaderLine = scrollTop > 20
      }
    },
    mounted() {
      this.$refs.body && on(this.$refs.body, 'scroll', this.handleScroll)
    },
    beforeDestroy() {
      this.$refs.body && off(this.$refs.body, 'scroll', this.handleScroll)
    }
  }
</script>