vue项目中-利用slot插槽封装页面结构组件

709 阅读2分钟
1、问题引入:在开发一个项目中,为了提升效率,通常会将项目中结构大体相同的页面封装。实现多次利用
2、实现的页面截图如下(如下截图已将页面拆分为小的模块,可以利用插槽配合组件的方式来实现)
2.1 这张截图展示的是利用动态注册组件的形式去渲染图片中的第4项(中间内容-表格部分)(图1

image.png

2.2 利用动态注册组件的形式去渲染图片中的第4项(中间内容-表格部分)代码截图如下:(图2

image.png

3 这张截图展示的是通过自定义列表内容(在这个项目中,由于图一的表格出现次数较多,所以将如下这种表格内容定义为自定义内容)的形式去渲染图片中的第4项(中间内容-表格部分)(图3

image.png

代码部分

1、公共组件BaseList
<template>
  <!-- 列表公共结构 -->
  <div>
    <!-- 1、顶部页面title(这个组件我就不放代码了,主要是样式。topTitle数据是通过父组件传递过来的) -->
    <CommonTitle :topTitle="topTitle"></CommonTitle>

    <!-- 2、顶部统计筛选(通过接收的isShowTopFilter数据,判断是否显示该项) -->
    <div v-if="isShowTopFilter" style="height:108px;margin-top:18px;overflow:hidden">
      <slot name="topFilter"></slot>
    </div>

    <!-- 主体部分 -->
    <div style="margin-top:20px;padding:20px 20px 0;background:#fff;border-radius:4px">
      <!-- 3、搜索部分 -->
      <div class="search">
        <div class="search-left">
          <!-- 3.1 搜索项 -->
          <slot name="searchOption"></slot>
          <el-button type="primary" size="small" @click="onSearch" style="margin-left:30px;">查找</el-button>
        </div>
        <!-- 3.2 搜索右边内容(右侧按钮) -->
        <slot name="searchOptionRight"></slot>
      </div>
      
      <!-- 4、列表部分 -->
      <div ref="contentWrap" class="content-wrap" :style="{height: contentHight + 'px'}">
        <div :style="{height: listBoxHight + 'px'}" class="list-box-wrap">
          <template v-if="isCustomTable">
            <div class="list-box">
              <slot name="tableContent"></slot>
            </div>
          </template>
          <template v-else>
            <ul v-if="tableList.length > 0" class="list-box">
                <li v-for="(item,index) in tableList" :key="index" class="item">
                  <component :is="tablItemComponent" :itemData="item" :index="index" :pageIndex="pageIndex" :pageSize="pageSize"></component>
                </li>
            </ul>
            <!-- 无数据时占位 -->
            <el-empty class="list-box" v-else :image-size="200" />
          </template>
        </div>

        <!-- 5、分页 -->
        <div ref="paginationWrap" style="display:flex;justify-content:flex-end;margin-right:30px">
          <!-- 这是分页控件,这里我就不放具体代码了。如想了解分页控件的封装,可查看我的文章:(暂时空着,后续补上) -->
        </div>
      </div>
    </div>

  </div>
</template>

<script>
// import CommonTitle from '@/components/dailySupervision/commonTitle'
// import Pagination from '@/components/pagination/index.vue'

export default {
  name: 'BaseList',
  components: {
    // Pagination,
    // CommonTitle
  },
  props: {
    // 页面顶部标题
    topTitle: {
      type: String,
      required: true,
      default: ''
    },
    // 是否自定义表格内容(slot name: tableContent)
    isCustomTable: {
      type: Boolean,
      default: false
    },
    // 列表数据
    tableList: {
      type: Array,
      required: true,
      default() {return []}
    },
    // 列表项组件名(存放在components/table-item中)
    tablItemComponentName: {
      type: String,
      required: true,
      default: ''
    },
    // 是否显示顶部统计筛选
    isShowTopFilter: {
      type: Boolean,
      default: true
    },
    pageIndex: {
      type: Number,
      default() {return 1}
    },
    pageSize: {
      type: Number,
      default() {return 10}
    },
    total: {
      type: Number,
      default() {return 0}
    }
  },
  data() {
    return {
      // 列表项组件
      tablItemComponent: '',
      // 列表 + 间距 + 分页高度
      contentHight: '300px',
      // 列表高度
      listBoxHight: '200px'
    }
  },
  computed: {

  },
  beforeMount() {
    // 注册组件
    // 在动态注册组件时也需要注意`import('../table-item/' + this.tablItemComponentName + '.vue')`import里写的就是文件的路径
   this.tablItemComponent=() => import('../table-item/' + this.tablItemComponentName + '.vue')
  },
  created() {

  },
  mounted() {
   this.$nextTick(() => {
      this.updateHeight()
      window.onresize = () => this.updateHeight()
    })
  },
  methods: {
    updateHeight() {
      const that = this
      if(!that.$refs.paginationWrap) {
        return
      }
      // 分页28px, 上下padding20px
      const paginationH =  that.$refs.paginationWrap.clientHeight // 28 + 20 + 20
      // 距离底部间距
      const bottomSpace = 10
      const boundingClientRect = that.$refs.contentWrap.getBoundingClientRect()
      that.contentHight = document.body.clientHeight - boundingClientRect.top - bottomSpace
      that.listBoxHight = that.contentHight - paginationH
      // 如果页面有切换动画,则需要延迟700左右,等待页面切换完毕
    },
    // 搜索按钮
    onSearch() {
      this.$emit('onSearch')
    },
    // 切换页事件
    handlePaginationChange(obj) {
      this.$emit('handlePaginationChange', obj)
    },
  }
}
</script>

<style lang="scss" scoped>
// 搜索区域
.search {
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  height:34px;margin-bottom:20px;overflow:hidden;
  .search-left {
    display: flex;
    align-items: center;
    .el-select:nth-child(n+2) {
      margin-left: 10px;
    }
  }
}
.list-box-wrap {
  // 整体内容上下内边距各20px, 顶部页面标题33px, 顶部统计高度固定126px(108+外面距18), 列表内容上下内边距各20px, 列表内容上外边距20px, 搜索部分高度54px(34+下外边距20), 分页68px, 留白34px
  // height:calc(100vh - 40px - 33px - 126px - 40px - 20px - 54px - 68px - 34px);
  overflow-y:auto;
  padding: 0 4px;
}
.list-box-wrap::-webkit-scrollbar-track-piece {
  //滚动条凹槽的颜色,还可以设置边框属性
  background-color: #f1f1f1;
}
.list-box-wrap::-webkit-scrollbar {
  //滚动条的宽度
  width: 3px;
  height: 10px;
}
 
.list-box-wrap::-webkit-scrollbar-thumb {
  //滚动条的设置
  background-color: #c1c1c1;
  background-clip: padding-box;
  min-height: 28px;
  border-radius: 8px;
}
 
.list-box-wrap::-webkit-scrollbar-thumb:hover {
  background-color: #a8a8a8;
}
.list-box {
  .item {
    padding: 28px 0;
    border-bottom: 1px solid #e5e5e5;
  }
  .flex1 {
    overflow: hidden;
  }
  .item:first-child {
    padding-top: 0;
  }
}
::v-deep .el-pagination {
  padding: 0 !important;
}
</style>
1.1 关于BaseList组件的注意事项
  • isCustomTable 是否自定义表格内容(slot name: tableContent)(为true时表示自定义) 如果在父组件中不传isCustomTable或传false时,则采用 图1 的表格形式通过tableList去渲染
  • tablItemComponentName 列表项组件名(存放在components/table-item中) 此项配合 isCustomTable和这行代码使用:
  beforeMount() {
    // 注册组件
   this.tablItemComponent=() => import('../table-item/' + this.tablItemComponentName + '.vue')
  },
  • 在动态注册组件时也需要注意import('../table-item/' + this.tablItemComponentName + '.vue')import里写的就是文件的路径

  • 函数中updateHeight的作用就是动态的计算列表区域的高度,实现整个页面列表区域Y轴滚动,其他内容固定。实现思想是根据分页容器往上的区域减去列表搜索栏区域、头部、分页高度等

第一种使用方式(isCustomTablefalse时,表示图1的列表形式)

2、使用该组件
<template>
  <div v-loading.fullscreen.lock="loading" class="content-root">
    <base-list 
      ref="baseList"
      topTitle='婚丧喜事台账'
      :tableList="tableList"
      :pageIndex="pageIndex"
      :pageSize="pageSize"
      :total="total"
      tablItemComponentName="WeddingsAndFuneralsTableItem"
      @onSearch="onSearch"
    >
      <template slot="topFilter">
        <!-- 2、顶部统计筛选 -->
        <div style="margin-top:10px">
          <!-- 这里是顶部统计筛选(可以配合组件使用) -->
        </div>
      </template>
      <!-- 3、搜索项 -->
      <template slot="searchOption">
        <!-- 3.1 这里是表格搜索项(可以配合组件使用) -->
      </template>
      <template slot="searchOptionRight">
        <!-- 3.2 搜索右边内容(按钮)(可以配合组件使用) -->
      </template>
    </base-list>
  </div>
</template>

<script>
// 公共列表组件
import BaseList from '@/components/base/BaseList.vue'
export default {
  name: "weddingsAndFuneralsList",
  components: {
    BaseList
  },
  data() {
    return {
      loading: false, // 是否展示loading
      pageIndex: 1,
      pageSize: 10,
      total: 0,
      tableList: []
    }
  },
  methods: {
    // 搜索事件
    onSearch() {
      this.pageIndex = 1
      // this.getPageList() // 查询列表数据
    },
  }
}
</script>
2.1 注意事项
  • tablItemComponentName 为列表组件的文件名称(isCustomTable为true时,tablItemComponentName也必传,但不会起作用。所以可以用一个已存在的组件名称)
  • onSearch 方法为BaseList组件中抛出的一个查询事件,用于搜索列表(这个地方可以将查询按钮不放在组件BaseList中,均可)

第二种使用方式(isCustomTabletrue时,表示图3的列表形式)

3、使用该组件(自定义表格内容图3)
<template>
  <div v-loading.fullscreen.lock="loading" class="content-root">
    <base-list 
      ref="baseList"
      topTitle='决策议题管理台账'
      :tableList="tableList"
      :pageIndex="pageIndex"
      :pageSize="pageSize"
      :total="total"
      :isCustomTable="true"
      tablItemComponentName="WeddingsAndFuneralsTableItem"
      @onSearch="onSearch"
    >
      <template slot="topFilter">
        <!-- 2、顶部统计筛选 -->
        <div style="margin-top:10px">
          <!-- 这里是顶部统计筛选(可以配合组件使用) -->
        </div>
      </template>
      <!-- 3、搜索项 -->
      <template slot="searchOption">
        <!-- 3.1 这里是表格搜索项(可以配合组件使用) -->
      </template>
      <template slot="searchOptionRight">
        <!-- 3.2 搜索右边内容(按钮)(可以配合组件使用) -->
      </template>
      <!-- 4 主体内容区域-表格部分(可以配合组件使用) -->
      <template> slot="tableContent">
      
      </template>
    </base-list>
  </div>
</template>

<script>
// 公共列表组件
import BaseList from '@/components/base/BaseList.vue'
export default {
  name: "IssueManageList",
  components: {
    BaseList
  },
  data() {
    return {
      loading: false, // 是否展示loading
      pageIndex: 1,
      pageSize: 10,
      total: 0,
      tableList: []
    }
  },
  methods: {
    // 搜索事件
    onSearch() {
      this.pageIndex = 1
      // this.getPageList() // 查询列表数据
    },
  }
}
</script>