1.src文件夹下的components-》table -》index.js
import MTable from './table.vue';
import MyTable from './table1.vue';

export default (Vue)=>{
  Vue.component("MTable", MTable);
myTable", MyTable)
}
2.src文件夹下的components-》table -》table1.vue      (组件)
 
/**
* 自定义table组件
********** columns:表头信息 **********
* @type: index -- 序号
* checkbox -- 多选框
* date -- 日期组件
* imgUpdata -- 图片上传
* select -- 下拉框
* button -- 按钮
* @width: 宽
* @title: 表头
* @thClass: 表头class
* @tdClass: 表列class
* @key: 字段名
* @onBlur: 失焦事件
* @onChange: change事件
* @onClick: click事件
* @tdClass: 表列class
* @option:{
optionList:下拉框的列表
optionValueKey: 绑定的value值得字段名,
optionLabelKey: [],数组最多2个label字段名
* }
* @filterable: 下拉框可搜索
* @disabled: 下拉框禁用
* @clearable: 可清空下拉框内容
* @butTitle: button名
*CheckboxGroup 是一个多选框组件
* 注:optionList必须获取到list再赋值
***************** tableData:列表数据 **********
******* tableCheck:多选框绑定行的index ********
***********************************************
* 具体参考addAdvanceOrde.vue文件 *
***********************************************
*/
<template>
<div class="me-table">
<CheckboxGroup v-model="selections" @on-change="selectionsChange">
<div class="ivu-table-wrapper" :style="height ? {height: height + 'px'} : ''">
<div class="ivu-table ivu-table-small ivu-table-border ivu-table-with-fixed-top" style="overflow:auto;min-height:80px">
<table cellspacing="0" cellpadding="0" border="0" style="width: 100%;">
<thead class="ivu-table-header">
<tr>
<th style="white-space: normal" class="ivu-table-column-center" v-for="(item, index) in columns" :width="item.width" :class="item.thClass">
<span v-if="item.type === 'index'">#</span>
<span v-else-if="item.type
<span v-else>{{item.title}}</span>
<span v-if="item.copyIcon">
<Icon type="ios-copy-outline" size="17" class="copyIcon" @click="copyFun(item.key)"></Icon>
</span>
</th>
</tr>
</thead>
<tbody class="ivu-table-tbody">
//tableData获取的后台列表数据  trItem数据用于v-model绑定数据  trIndex下标哪一行
<tr class="ivu-table-row" v-for="(trItem, trIndex) in tableData">
//tdItem 遍历的是 使用组件页面的columnsEdit的数据 
<td class="ivu-table-column-center" v-for="(tdItem, tdIndex) in columns" :class="tdItem.tdClass">
<span v-if="tdItem.type === 'index'">{{trIndex + 1}}</span>
<span v-else-if="tdItem.type === 'checkbox'">
<Checkbox :label="trIndex"><span> </span></Checkbox>
</span>
<!--
-----
------
 
组件页面中的<span  tdItem.type === 'input' && tdItem.onChange  && tdItem.onKeyUp
<Input v-model="trItem[tdItem.key]" :placeholder="tdItem.placeholder" :class="tdItem.inputClass" @on-blur="tdItem.onBlur(trItem, trIndex)"$event, trItem, trIndex)" />
 </span>
是判断使用组件页面的:columns="columnsEdit"中对象中的type:是否为input 并且有onChange  onKeyUp  属性  如果有就调用span这个组件元素
调用组件的页面
<my-table v-if="btnType[0]" :columns="columnsEdit" :tableData="tableData" :tableCheck="selects" height="650"></my-table>
this.columnsEdit = [
{
title: '工厂报价',
type: 'input',
width: '130',
key: 'factoryQuote',//和后端的key对应也就是 tableData遍历过后的  trItem[tdItem.key]
thClass: 'headerColor',
inputClass: this.message === 1 ? 'table_input normal_1 input_color1' : this.message === 2 ? 'table_input normal_1 input_color2' : 'table_input normal_1',
placeholder: this.message === 2 ? '未查询到核价单' : this.message === 1 ? '查询到多条结果请确认' : '',
onBlur: this.factoryQuoteChange,
onKeyUp: this.show,
copyIcon: true
}
]
 下面这么多的span  v-if   v-else-if ....是为了判断调用组件的页面用哪一个span
------
------
-->
<span v-else-if="tdItem.type === 'input' && tdItem.onChange">
<Input v-model="trItem[tdItem.key]"
</span>
<span v-else-if="tdItem.type === 'input' && tdItem.onKeyUp">
<Input v-model="trItem[tdItem.key]"
</span>
<span v-else-if="tdItem.type === 'input' && tdItem.onKeyUp && tdItem.onBlur">                                                                                              //键盘事件
<Input v-model="trItem[tdItem.key]" :placeholder="tdItem.placeholder" :class="tdItem.inputClass" @on-blur="tdItem.onBlur(trItem, trIndex)" @keyup.native="tdItem.onKeyUp($event, trItem, trIndex)"
</span>
<span v-else-if="tdItem.type === 'input' && tdItem.onBlur">
<Input v-model="trItem[tdItem.key]"
</span>
<!--日期-->
<span v-else-if="tdItem.type === 'date'">
  <DatePicker type="date" :value="trItem[tdItem.key]" @on-change="trItem[tdItem.key]=$event" :editable="false" clearable></DatePicker>
</span>
<span v-else-if="tdItem.type === 'input' && !tdItem.onChange && !tdItem.onBlur && !tdItem.onKeyUp">
<Input v-model="trItem[tdItem.key]" :placeholder="tdItem.placeholder" :maxlength="tdItem.maxlength" />
</span>
<span v-else-if="tdItem.type === 'select' && tdItem.onChange">
<Select ref="select" v-model="trItem[tdItem.key]" @on-change="tdItem.onChange(trItem, trIndex)" :filterable="tdItem.filterable" :disabled="tdItem.disabled"
:clearable="tdItem.clearable">
<Option v-for="item in tdItem.option.optionList" :value="item[tdItem.option.optionValueKey]" :key="item[tdItem.option.optionKey]?item[tdItem.option.optionKey]:item[tdItem.option.optionValueKey]"
:label="tdItem.option.optionLabelKey.length === 1 ? item[tdItem.option.optionLabelKey[0]] : tdItem.option.optionLabelKey.length === 2 ?item[tdItem.option.optionLabelKey[0]] + '-' + item[tdItem.option.optionLabelKey[1]]:item[tdItem.option.optionLabelKey[0]] + '-' + item[tdItem.option.optionLabelKey[1]]+'-'+item[tdItem.option.optionLabelKey[2]]"></Option>
</Select>
</span>
<span v-else-if="tdItem.type === 'select' && !tdItem.onChange">
<Select ref="select1" v-model="trItem[tdItem.key]" :filterable="tdItem.filterable" :disabled="tdItem.disabled" :clearable="tdItem.clearable">
<Option v-for="item in tdItem.option.optionList" :value="item[tdItem.option.optionValueKey]" :key="item[tdItem.option.optionKey]?item[tdItem.option.optionKey]:item[tdItem.option.optionValueKey]"
:label="tdItem.option.optionLabelKey.length === 1 ? item[tdItem.option.optionLabelKey[0]] : tdItem.option.optionLabelKey.length === 2 ?item[tdItem.option.optionLabelKey[0]] + '-' + item[tdItem.option.optionLabelKey[1]]:item[tdItem.option.optionLabelKey[0]] + '-' + item[tdItem.option.optionLabelKey[1]]+'-'+item[tdItem.option.optionLabelKey[2]]"></Option>
</Select>
</span>
<span v-else-if="tdItem.type === 'button'">
<Button type="info" @click="tdItem.onClick(trItem, trIndex)" size="small">{{tdItem.butTitle}}</Button>
</span>
<span v-else v-html="trItem[tdItem.key]"></span>
</td>
</tr>
</tbody>
</table>
<div v-if="!tableData.length" class="no-data">暂无数据</div>
</div>
</div>
</CheckboxGroup>
</div>
</template>

<script>
  import {
    getImgBlobSrc
  } from '@/libs/util'

  export default {
//导出的tableData columns tableCheck....都是需要父页面传的数据,(:绑定的数据)
myTable',
    props: {
      // 选择供应商之后增加明细的数据(更改供应商时可以获取到该数据)
Data:
        type: Array,
        default () {
          return []
        }
      },
      height: {
        type: String
      },
      // 点击增加明细之后 弹框确定之后需要添加的table数据
mns:
        type: Array,
        default () {
          return []
        }
      },
      tableCheck: {
        type: Array,
        default () {
          return []
        }
      }
    },
    data() {
      return {
        modelData: [],
        selections: this.tableCheck,
        imgIndex: '',
        imgKey: ''
      }
    },
    mounted() {
      let self = this
      this.$(".ivu-table-row").hover(function () {
        self.$(this).addClass('ivu-table-row-hover')
      }, function () {
        self.$(this).removeClass('ivu-table-row-hover')
      })
    },
    methods: {
      selectionsChange() {
        this.tableCheck.splice(0, this.tableCheck.length)
        this.selections.forEach(ele => {
          this.tableCheck.push(ele)
        })
      },
      copyFun(key) {
        if(key === 'qualifiedQuantity'){
          this.tableData.forEach(ele => {
            this.$set(ele, key, ele.quantity)
            ele.unqualifiedQuantity = ele.qualifiedQuantity && ele.quantity ? Number(ele.quantity) - Number(ele.qualifiedQuantity) : ''
          })
          return
        }
        if (!this.tableData[0][key] && this.tableData[0][key] != 0) {
          this.$Message.warning('第一行值为空,不能复制!')
          return
        }
        this.tableData.forEach(ele => {
          this.$set(ele, key, this.tableData[0][key])
        })
        if (key === 'taxUnitPrice' || key === 'taxRate') {
          this.tableData.forEach(ele => {
            if ((ele.taxUnitPrice || ele.taxUnitPrice == 0) && (ele.taxRate || ele.taxRate == 0)) {
              let unitPrice = ele.taxUnitPrice / (1 + ele.taxRate / 100)
              this.$set(ele, 'unitPrice', unitPrice.toFixed(4))
            }
          })
        }
      },
      uploadImg(file) {
        let formData = new FormData()
        formData.append('file', file)
        this.$axios({
          url: '/chenfan_api/file/upload',
          method: 'post',
          data: formData
        }).then((data) => {
          if (data.code === 200) {
            this.$set(this.tableData[this.imgIndex], [this.imgKey], data.obj[0].id)
          }
        })
        return false
      },
      getImgIndex(index, key) {
        this.imgIndex = index
        this.imgKey = key
      },
      removeImg(key, index) {
        this.tableData[index][key] = ''
      }
    },
    watch: {
      tableCheck(newVal, oldVal) {
        this.selections = newVal
      }
    }
  }

</script>

<style lang="less">
  .me-table {
    .ivu-table-wrapper {
      min-height: 80px;
    }

    .ivu-table-row {
      td {
        padding: 0 5px;
      }
    }

    .no-data {
      position: absolute;
      top: 50%;
      left: 50%;
      font-size: 14px;
    }

    .demo-upload-list {
      display: inline-block;
      width: 70px;
      height: 70px;
      text-align: center;
      line-height: 70px;
      border: 1px solid transparent;
      border-radius: 4px;
      overflow: hidden;
      background: #fff;
      position: relative;
      box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
      margin-right: 4px;
    }

    .demo-upload-list img {
      width: 100%;
      height: 100%;
    }

    .demo-upload-list-cover {
      display: none;
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background: rgba(0, 0, 0, .6);
    }

    .demo-upload-list:hover .demo-upload-list-cover {
      display: block;
    }

    .demo-upload-list-cover i {
      color: #fff;
      font-size: 20px;
      cursor: pointer;
      margin: 0 2px;
    }

    .span-img {
      display: inline-block;
      padding: 5px 0;
    }

    .ivu-checkbox-group-item {
      width: 20px;
      height: 20px;
      overflow: hidden;
    }
  }

</style> 
 
3.使用组件的页面
<Card style="margin-top: 20px;">
<!-- table1组件 -->
<my-table v-if="btnType[0]" :columns="columnsEdit"leData="tableData" :tableCheck="selects" height="650"></my-table>
</Card>
//tableData获取的后台列表数据
 tableData:[  ],
//编辑
this.columnsEdit
{
type: 'index',
width: '40'
}, {
type: 'checkbox',
width: '40'
}, {
title: '品牌',
width: '100',
key: 'brandName'
},
{
title: '货号',
width: '110',
key: 'productCode'
}, {
title: '存货编码',
width: '120',
key: 'inventoryCode',
thClass: 'headerColor'
}, {
title: '存货名称',
width: '160',
key: 'inventoryName'
}, {
title: '颜色',
width: '100',
key: 'color'
}, {
title: '尺码',
width: '100',
key: 'size'
}, {
title: '季节',
width: '100',
key: 'season'
}, {
title: '工厂报价',
type: 'input',
width: '130',
key: 'factoryQuote',
thClass: 'headerColor',
inputClass: this.message === 1 ? 'table_input normal_1 input_color1' : this.message === 2 ? 'table_input normal_1
placeholder: this.message === 2 ? '未查询到核价单' : this.message === 1 ? '查询到多条结果请确认' : '',
onBlur: this.factoryQuoteChange,
onKeyUp: this.show,//this.show是一个方法
copyIcon: true
}, {
title: '含税单价',
type: 'input',
width: '130',
key: 'taxUnitPrice',
thClass: 'headerColor',
onBlur: this.taxUnitPriceChange,
onKeyUp: this.show,
placeholder: this.message === 2 ? '未查询到核价单' : this.message === 1 ? '查询到多条结果请确认' : '',
inputClass: this.message === 1 ? 'table_input normal_2 input_color1' : this.message === 2 ? 'table_input normal_2
copyIcon: true
}, {
title: '不含税单价',
width: '100',
key: 'unitPrice'
}, {
title: '税率',
type: 'select',
width: '110',
key: 'taxRate',
option: {optionList: taxRateList, optionValueKey: 'key', optionLabelKey: ['key']},
thClass: 'headerColor',
onChange: this.taxRateChange,
copyIcon: true
}, {
title: '调价原因',
type: 'input',
width: '160',
key: 'remark',
thClass: 'headerColor',
onKeyUp: this.show,
inputClass: 'table_input normal_3',
copyIcon: true
},{

            title: '合同初始交期',
            type: 'date',
            width: '150',
            key: 'conStartDate',
            copyIcon: true,
            thClass: 'headerColor'
  }
]
 
 
// 删除行
deleteTrFun () {
let _arr = []
if (!this.selects.length) this.$Message.warning('请先选择行!')

        this.tableData.forEach((ele, index) => {
          console.log(this.selects, 22)
          if (this.selects.indexOf(index) < 0) _arr.push(ele)
        })
        this.productList.splice(this.selects, 1)
        console.log(this.productList, 333)
        this.selects = []
        this.tableData = _arr
        if(!this.tableData.length){
          this.btnType[1] = true
        }
      },
 
 
// 键盘触发事件  item==组件里的$event 也就是当前元素this    trItem== row 这一条数据  trIndex===index第几行
show (item, row, index) {
let newIndex;
// 通过ev获取当前input的class名称,用于后期判断属于哪列 
let className = item.target.offsetParent.className;
console.log(item, index);
// 每一列 inputClass中的normal_1 normal_2 normal_3 是用来判断当前焦点在哪个input的上  用于后期上下左右键盘可以控制移动到某个input
if (className.indexOf('normal_1') !== -1) {
this.data = index;
this.didata = index*3;
newIndex = index*3;
} else if (className.indexOf('normal_2') !== -1) {
this.data = index;
this.didata = index*3 + 1;
newIndex = index*3 + 1;
} else if (className.indexOf('normal_3') !== -1) {
this.data = index;
this.didata = index*3 + 2;
newIndex = index*3 + 2;
}
// 获取所有input
let inputAll = document.querySelectorAll('.table_input input');
this.iddata = inputAll;
// 向上 =38
if (item.keyCode === 38) {
newIndex -= 3;
if (inputAll[newIndex]) {
inputAll[newIndex].focus();
}
}
// 向下 =40
if (item.keyCode === 40) {
newIndex += 3;
if (inputAll[newIndex]) {
inputAll[newIndex].focus();
}
}
// 向左
if (item.keyCode === 37) {
newIndex -= 1;
if (inputAll[newIndex]) {
inputAll[newIndex].focus();
}
}
// 向右
if (item.keyCode === 39) {
newIndex += 1;
if (inputAll[newIndex]) {
inputAll[newIndex].focus();
}
}
}