封装组件的步骤

  1.   建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。    
  2.   准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
  3.   准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
  4.   封装完毕了,直接调用即可。

Vue组件间的参数传递

1.父组件与子组件传值

   父组件传给子组件:子组件通过props方法接受数据;

  子组件传给父组件:$emit方法传递参数

2.非父子组件间的数据传递,兄弟组件传值

  Vue 官方提供的 Vuex 框架   

  发布订阅模式(总线机制 / Bus / 观察者模式)

  eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。

 

我写table组件的思路:

1.样式定义

  • 内容间距(innerSpacing):big(16px)、middle(12px)、small(8px)、min(4px); 默认:middle
  • 有无边框(frame):true/false; 默认:false
  • 有无(rowHover):true/false; 默认:false
  • 是否为斑马纹(stripe):true/false; 默认:true
  • 是否固定表头(isfixedThead); 默认:true
  • 对其方式 内容(align)/表头(headerAlign):left center right;  默认:center

2. 选中数据(checkbox)父组件调用:selected-item.sync="selectedItem";selectedItem=[];

  •   通过单向数据流,外界好传入一个selectedItem,table接受这个selectedItem,默认为空数组
  •   单选:对表单内容的checkbox添加change事件onChangeItem把每一列行的’索引‘,’项‘还有原生event,
  • 如果是选中状态就把传入的所有数据的数组tableTbodyData作为参数通过refs.a.indeterminate = true,其他情况都等于false

3.slot插槽:可用于操作、按钮等等

  • 例如:表头<th slot="operating-name">操作</th>
  •  列 <td slot="operating-trigger" slot-scope="row"> <button>{{row.item.name}}</button></td>

代码

建立组件的模板

<template>
  <div
    :class="[
                isfixedThead?'D-fixed-thead-style':'D-default-table',
                frame?'border-frame':'',
                rowHover?'rowhover-bgcolor':'',
                align+'AlignInner',
                headerAlign+'HeaderAlignInner'
              ]"
  >
    <div class="table-head">
      <table class="default-head">
        <!-- <colgroup>
          <col :span="Object.keys(this.tableTbodyData[0]).length"  :style="'width:'+(100/Object.keys(this.tableTbodyData[0]).length)+'%'">
        </colgroup>-->
        <thead v-if="isShowThead" :class="innerSpacing">
          <slot v-if="this.tableTheadData.length==0">
            <tr>
              <!-- 无数据显示 -->
              <th>{{$t('NoData')}}</th>
            </tr>
          </slot>
          <tr>
            <th v-if="isCheck" style="width:40px">
              <input
                type="checkbox"
                @change="onChangeItemAll($event)"
                ref="all"
                :checked="areAllItemChecked"
              />
            </th>
            <th v-for="(thead,i) in tableTheadData" :key="'thead-'+i">
              <input v-show="isCheckItem" type="checkbox" @change="onChangeColumn(thead, i, $event)" :checked="onCheckedColumnItem(thead)"/>
              {{thead.name}}
            </th>
            <slot name="operating-name"></slot>
          </tr>
        </thead>
      </table>
    </div>

    <div class="table-body">
      <table class="default-body">
        <tbody :class="innerSpacing">
          <slot v-if="this.tableTbodyData.length==0">
            <tr>
              <td>{{$t('NoData')}}</td>
            </tr>
          </slot>
          <slot v-show="changeShowType" name="table-tr" v-for="(tbody_td,tr) in tableTbodyData" :item="tbody_td"></slot>
          <tr
            v-show="!changeShowType"
            v-for="(tbody_td,tr) in tableTbodyData"
            :key="'tbody-tr-'+tr"
            :class="tr%2&&stripe?'table-interval-background':''"
          >
            <td v-if="isCheck" style="width:40px;">
              <input
                type="checkbox"
                @change="onChangeItem(tbody_td, tr, $event)"
                :checked="onChecked(tbody_td)"
              />
            </td>
            <td v-for="(thead,td) in tableTheadData" :key="'tbody-td-'+td">{{tbody_td[thead.val]}}</td>
            <slot name="operating-trigger" :item="tbody_td"></slot>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

props传值,以及逻辑

<script>
export default {
  props: {
    isShowThead: {
      //是否显示表头
      type: Boolean,
      default: true
    },
    //更改行内某一项的显示形式
    changeShowType: {
      type: Boolean,
      default: false
    },
    isfixedThead: {
      //是否固定表头
      type: Boolean,
      default: true
    },
    isCheck: {
      //是否显示选中数据(单选/全选)
      type: Boolean,
      default: false
    },
    isCheckItem: {
      //是否显示选中数据(单选/全选)
      type: Boolean,
      default: false
    },
    selectedItem: {
      type: Array,
      default: () => []
    },
    align: {
      type: String,
      default: "center"
    },
    headerAlign: {
      type: String,
      default: "center"
    },
    innerSpacing: {
      type: String,
      default: "middle"
    },
    frame: {
      //是否显示边框
      type: Boolean,
      default: false
    },
    rowHover: {
      //Hover
      type: Boolean,
      default: false
    },
    stripe: {
      //斑马纹
      type: Boolean,
      default: true
    },
    tableTheadData: {
      type: [Object, Array],
      default: () => []
    },
    tableTbodyData: {
      type: [Object, Array],
      default: () => []
    }
    //选中的列
    // onColumnItem: {
    //   type: Array,
    //   default: () => []
    // }
  },
  data() {
    return {
      tdWidth: 0,
      tdLength: [],
      onColumnItem:[]
    };
  },
  watch: {
    // 用于监听checkbox 改变选中的行状态
    selectedItem() {
      // if (this.selectedItem.length === this.tableTbodyData.length) {
      //   this.$refs.all.indeterminate = false;
      // } else if (this.selectedItem.length === 0) {
      //   this.$refs.all.indeterminate = false;
      // } else {
      //   this.$refs.all.indeterminate = true;
      // }
    },
    onColumnItem(){}
  },
  computed: {
    // 判断这两个数组里的所有项的id是否都相等来判断是否全选
    // 先对这两个数组里的id进行排序,sort会改变原来的数组,所以我们需要先用map生成一个新的数组,然后在排序
    areAllItemChecked() {
      const a = this.tableTbodyData.map(n => n.id).sort();
      const b = this.selectedItem.map(n => n.id).sort();
      if (a.length === b.length) {
        let result = false;
        for (let i = 0; i < a.length; i++) {
          if (a[i] !== b[i]) {
            result = false;
            break;
          } else {
            result = true;
          }
        }
        return result;
      } else {
        return false;
      }
    }
  },
  mounted() {},
  methods: {
    // checkbox全选方法
    onChangeItemAll(e) {
      console.log("checkbox checked", e);
      if (e.target.checked) {
        this.$emit("update:selectedItem", this.tableTbodyData);
      } else {
        this.$emit("update:selectedItem", []);
      }
    },
    // checkbox 行选择
    onChangeItem(item, index, e) {
      let copy = JSON.parse(JSON.stringify(this.selectedItem));
      if (e.target.checked) {
        copy.push(item);
      } else {
        //取消选中状态:点击当前的checkbox保留数组中id不等于当前id的项
        copy = copy.filter(i => i.id !== item.id);
      }
      this.$emit("update:selectedItem", copy);
    },
    onChecked(item) {
      return this.selectedItem.filter(n => n.id === item.id).length > 0
        ? true
        : false;
    },
    // 选择表头的列
    onChangeColumn(item, index, e){
      if (e.target.checked) {
        this.onColumnItem.push(item);
      } else {
        //取消选中状态:点击当前的checkbox保留数组中id不等于当前id的项
        this.onColumnItem = this.onColumnItem.filter(i => i.val !== item.val);
      }
      this.$emit("onChangeColumn", this.onColumnItem);
    },
    onCheckedColumnItem(item) {
      return this.onColumnItem.filter(n => n.val === item.val).length > 0
        ? true
        : false;
    }
  }
};
</script>

stylus样式

@import '../index'; //调用的是公用样式,这里就不写了

// peiyu: 2019.12.24 --> 2019.12.27
// 内容间距(innerSpacing):big(16px)、middle(12px)、small(8px)、min(4px); 默认:middle
$innerSpacing-big = 16px 8px;
$innerSpacing-middle = 12px 6px;
$innerSpacing-small = 8px 4px;
$innerSpacing-min = 4px 2px;
.big{                              
    th,td{
        padding: $innerSpacing-big 
    }                   
} 
.middle{                              
    th,td{                   
        padding: $innerSpacing-middle 
    }                   
}  
.small{                              
    th,td{
        padding: $innerSpacing-small 
    }                   
}
.min{                              
    th,td{
        padding: $innerSpacing-min 
    }                   
} 

// 有无边框
.border-frame{
    tr{
        border: 1px solid rgba(255, 255, 255, 0.32);
    }
} 

// 有无hover
.rowhover-bgcolor {
    .table-body{
        tr{
            &:hover{
                background: $table-hover-bgColor                
            }
            
        }
    }
} 
// 斑马纹
.table-interval-background{
    background: $table-interval-bgColor
}   

// 对其方式 内容(align)/表头(headerAlign):left center right;  默认:center
.centerAlignInner{
    th{
        text-align: center
    }    
}
.leftAlignInner{
    th{
        text-align: left
    }   
}
.rightAlignInner{
    td{
        text-align: right
    }   
}
.centerHeaderAlignInner{
    td{
        text-align: center
    }    
}
.leftHeaderAlignInner{
    td{
        text-align: left
    }   
}
.rightHeaderAlignInner{
    td{
        text-align: right
    }   
}
// 不换行
th,td{
    overflow: hidden; 
    text-overflow:ellipsis;  
    white-space: nowrap;
}


// 默认样式
.D-default-table {
    height : 100%;
    .table-head{
        font-size: $font-prompt-size;
        color: $font-coloc-Secondary;
        .default-head{
            table-layout: fixed;
            border-collapse: collapse;
            width: 100%;       
            border-bottom: 1px solid $dividing-line-color;                                
        }        
    }
    .table-body{
        font-size: $font-secondary-size;
        color: $font-coloc-ordinary
        .default-body{
            table-layout: fixed;
            width: 100%;
            border-collapse: collapse;                           
        }        
    }   
}

// 固定表头
.D-fixed-thead-style{
    scrollBar();
    height : 100%;
    width: 100%;
    overflow : hidden;
    display : flex;
    flex-direction: column;
    .table-head{
        font-size: $font-prompt-size;
        color: $font-coloc-Secondary;  
        padding-right: 6px;              
        // 默认样式
        .default-head{
            table-layout: fixed;
            border-collapse: collapse;
            width: 100%; 
            color: $font-coloc-Secondary 
            border-bottom: 1px solid $dividing-line-color;                                                 
        }        
    }
    .table-body{
        font-size: $font-secondary-size;
        color: $font-coloc-ordinary
        flex-grow : 1;
        overflow-y : scroll; 
        // overflow-y: auto; 
        .default-body{
            table-layout: fixed;
            width: 100%;
            border-collapse: collapse;           
        }        
    }
} 

// checkbox

input[type="checkbox"]{
    -webkit-appearance: none;
    vertical-align:middle;
    margin-top:0;
    background:rgba(255,255,255,0.1);
    border:$font-coloc-Secondary solid 1px;
    border-radius: 3px;
    min-height: 16px;
    min-width: 16px;
    cursor:pointer;
}
input[type="checkbox"]:checked {
    background: $color-caption;
}
input[type=checkbox]:checked::after{ 
    content: '';
    position: absolute;
    background: transparent;
    border: #fff solid 2px;
    border-top: none;
    border-right: none;
    height: 0.45rem;
    width: 0.78rem;
    -moz-transform: rotate(-45deg);
    -ms-transform: rotate(-45deg);
    -webkit-transform: rotate(-45deg); 
    transform: rotate(-45deg);
}
.table-head{
    input[type=checkbox]:checked::after{ 
        content: '';
        position: absolute;
        background: transparent;
        border: #fff solid 2px;
        border-top: none;
        border-right: none;
        height: 0.45rem;
        width: 0.78rem;
        -moz-transform: rotate(-45deg);
        -ms-transform: rotate(-45deg);
        -webkit-transform: rotate(-45deg); 
        transform: rotate(-45deg);
    }
}