二次封装table组件

149 阅读5分钟

二次封装table组件有两种封装途径

1.先定义好位置,用插槽解决

<template>
    <div class="maincontent">
      <!-- 筛选条件 -->
      <div class="main-topSearch">
        <slot name="search"></slot>
      </div>
      <div class="main-center">
        <!-- 常用按钮 -->
        <div class="btns">
            <slot name="btns"></slot>
        </div>
        <!-- 正文 -->
        <div class="maincenter-table">
            <slot name="tabs"></slot>
            <slot name="table"></slot>
        </div>
      </div>
    </div>  
</template>

<style lang="scss" scoped>
@import "./index.scss"
</style>

样式

$main-color: #07BFB4;

// ---------------------------------------------只写关于通用页面的样式-----------------------------------------
.maincontent {

    .main-topSearch {
        display: block;
        background: #fff;
        padding: 20px 17px 0px;
    }
    
    // 表格
    ::v-deep .el-tabs--border-card {
        -webkit-box-shadow: none;
        box-shadow: none
    }

    .main-center {
        background-color: white;
        border-radius: 2px;
        margin-top: 10px;
        padding: 0px 20px 10px 20px;

        .maincenter-top {
            display: flex;
            justify-content: space-between;
            padding-right: 0px;
            padding-left: 20px;
            padding-top: 5px;
            padding-bottom: 3px;

            .title {
                font-size: 14px;
                color: #303133;
                line-height: 20px;
            }

            .rightbutton {
                width: 80px;
                height: 40px
            }

            .sortOne {
                padding: 0px 0px 7px 0px;
                border-bottom: none;
                color: #999;
                cursor: pointer; //鼠标手
                font-size: 14px;
                font-weight: 500;
            }

            .sortOneSelected {
                padding: 0px 0px 7px 0px;
                border-bottom: 3px solid $main-color;
                color: #333;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
            }

            .sortTwo {
                padding: 0px 0px 7px 0px;
                margin-left: 30px;
                color: #999;
                border-bottom: none;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
            }

            .sortTwoSelected {
                padding: 0px 0px 7px 0px;
                margin-left: 30px;
                color: #333;
                border-bottom: 3px solid $main-color;
                cursor: pointer;
                font-size: 14px;
                font-weight: 500;
            }

        }
        .btns,
        .filters {
            display: flex ;
            justify-content: space-between;
            align-items: center;
            padding: 20px 0px !important;
            margin-bottom: 0px !important;
            background: #fff;
            align-items: center;
            .btn{
                font-weight: 400;
                color: #07BFB4;
                border: 1px solid #07BFB4;
                border-radius: 2px;
            
                ::v-deep span{
                  display: flex;
                  align-items: center;
                  justify-content: center;
            
                  .icon{
                    width: 14px;
                    margin-right: 5px;
                  }
                }
            }
        }
        .maincenter-table {
            ::v-deep .el-tabs--border-card>.el-tabs__content {
                padding: 20px;
            }
            ::v-deep .el-tabs__header {
                margin: 0 0 20px;
            }
            ::v-deep .el-table--small {
                border: 1px solid #DEE2E7;
            }

            ::v-deep .el-tabs--card>.el-tabs__header .el-tabs__item.is-active {
                border-bottom-color: white !important;
                /* z-index: 99; */
                background: white;
                color: #07BFB4;
            }

            ::v-deep .el-tabs--card>.el-tabs__header .el-tabs__item {
                background: #F4F4F4;
                color: #777777;
                border-bottom: 1px solid white;
                // border-left: 1px solid #E4E7ED;
            }

            //分页设置
            .maincenter-bottom {
                background-color: white;
                height: 52px;
                display: flex;
                justify-content: space-between;
                align-items: center;

                .bottom_title {
                    margin-left: 20px;
                    color: #606266;
                    font-size: 13px;
                    line-height: 32px;
                }

                .bottonm-page {
                    margin-top: 20px;
                    margin-right: 20px;
                    margin-bottom: 10px;
                }

            }
        }

        .blank_tips {
            height: 140px;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
        }

        .el-table--enable-row-hover .el-table__body tr:hover>td {
            background-color: #f5f7fa !important;
        }

    }

}

页面使用

<template>
    <div class="main_content">
      <TablePage>
        <template #search>
          <el-form
            label-position="right"
            ref="form"
            :model="formSearch"
            label-width="100px"
          >
            <el-row :gutter="20" type="flex">
              <el-col :span="20">
                <el-row :gutter="20" type="flex">
                  <el-col :span="8">
                    <el-form-item label="用户名" class="mb0">
                      <el-input v-model="formSearch.nickname" />
                    </el-form-item>
                  </el-col>
                  <el-col :span="8">
                    <el-form-item label="注册成功时间" class="mb0">
                      <el-date-picker
                        type="daterange"
                        v-model="time"
                        style="width: 100%"
                        value-format="yyyy-MM-dd"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                      />
                    </el-form-item>
                  </el-col>
                  <el-col :span="8">
                    <el-form-item label="项目地址:" class="mb0">
                      <div style="display: flex; width: 100%">
                        <el-select
                          v-model="formSearch.cityId"
                          placeholder="市/州"
                          class="formItem1"
                          clearable
                          @change="selectCity"
                        >
                          <el-option
                            v-for="item in locationCityCodeOptions"
                            :key="item.deptId"
                            :label="item.name"
                            :value="item.deptId"
                          />
                        </el-select>
                        <div style="width: 10px"></div>
                        <el-select
                          v-model="formSearch.countyId"
                          placeholder="区/县"
                          class="formItem1"
                          clearable
                        >
                          <el-option
                            v-for="item in locationAreaCodeOptions"
                            :key="item.deptId"
                            :label="item.name"
                            :value="item.deptId"
                          />
                        </el-select>
                      </div>
                    </el-form-item>
                  </el-col>
                </el-row>
              </el-col>
              <el-col :span="4">
                <el-button
                  type="primary"
                  @click="queryData(1)"
                  icon="el-icon-search"
                >
                  筛选
                </el-button>
                <el-button @click="clearData" icon="el-icon-delete">
                  清空
                </el-button>
              </el-col>
            </el-row>
          </el-form>
        </template>
        <template #table>
          <el-table
            :data="tableData"
            style="border: 1px solid #f4f4f4"
            :header-cell-style="{ background: '#F4F4F4', color: '#000' }"
            v-loading="loading"
          >
            <el-table-column
              prop="nickname"
              header-align="center"
              align="center"
              label="用户名称"
            />
            <el-table-column
              prop="cityName"
              header-align="center"
              align="center"
              label="注册所在市/州"
            />
            <el-table-column
              prop="countyName"
              header-align="center"
              align="center"
              label="注册所在区/县"
            />
            <el-table-column
              prop="registerTime"
              header-align="center"
              align="center"
              label="注册成功时间"
            />
            <el-table-column
              prop="loginLastTime"
              header-align="center"
              align="center"
              label="最后登录时间"
            />
            <el-table-column
              prop="productCount"
              header-align="center"
              align="center"
              label="项目数量"
            >
              <template slot-scope="scope">
                <el-popover placement="top-start" width="120" trigger="hover">
                  <div>
                    <p class="mt0"><b>项目数量(个)</b></p>
                    <p style="border-bottom: 1px solid black"></p>
                    <p class="flex-p">
                      <span>储备项目</span>
                      <span>{{ scope.row.reserveCount }}</span>
                    </p>
                    <p class="flex-p">
                      <span>在建项目</span>
                      <span>{{ scope.row.constructionCount }}</span>
                    </p>
                    <p class="flex-p mb0">
                      <span>运行项目</span>
                      <span>{{ scope.row.connectionCount }}</span>
                    </p>
                  </div>
                  <span style="cursor: pointer" slot="reference">{{
                    scope.row.productCount
                  }}</span>
                </el-popover>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="100">
            <template slot-scope="scope">
              <el-popconfirm  title="确定重置密码吗?"  style="margin-left: 10px" @confirm="handlePsWord(scope.row, scope.index)">
                  <el-button  type="text"  slot="reference"
                      class="tableButtonEdit">重置密码</el-button>
              </el-popconfirm>
            </template>
          </el-table-column>
           
          </el-table>
          <div class="avue-crud__pagination">
            <el-pagination
              @size-change="sizeChangeHandle"
              @current-change="currentChangeHandle"
              :current-page="pagination.currentPage"
              :page-sizes="pageSizes"
              :page-size="pagination.size"
              :total="total"
              background
              layout="total, sizes, prev, pager, next, jumper"
            />
          </div>
        </template>
      </TablePage>
    </div>
  </template>
  
  <script>
  import TablePage from '@/components/TabePage/page.vue';
//   import { queryCitiesAndAreas } from '@/api/project/submit/submit';
//   import { getEnterpriseUserPage } from '@/api/admin/user';
//   import {resetPassword } from '@/api/admin/user'
//   import axios from '@/router/axios';
  const pageSizes = [10, 20, 50, 100];
  export default {
    data() {
      return {
        formSearch: {
          nickname: '',
          cityId: '',
          countyId: '',
          registerEndTime: '',
          registerStartTime: ''
        },
        time: '',
        locationCityCodeOptions: [],
        locationAreaCodeOptions: [],
        pageSizes,
        pagination: {
          current: 1,
          size: pageSizes[0],
        },
        tableData: [],
        total: 0,
        loading: false
      };
    },
    components: {
      TablePage
    },
    mounted() {
    //   console.log(axios.defaults.baseURL,'--------axios.defaults.baseURL')
    //   queryCitiesAndAreas()
    //     .then((res) => {
    //       const data = res.data.data;
    //       this.locationCityCodeOptions = data.filter((x) => x.level === 3);
    //       this.locationAreaCodeOptionsAll = data.filter((x) => x.level === 4);
    //       this.queryData();
    //     })
    //     .catch(() => {});
    },
    methods: {
      handlePsWord(row, index) {
         console.log('-----------row')
         console.log(row)
        //  resetPassword({
        //   userId:row.userId
        //  })
        //   .then(() => {        
        //     // this.role = []
        //     // this.getList(this.page)
        //     // done()
        //     this.$notify.success('修改成功')
        //   })
        //   .catch(() => {
        //     // loading()
        //   })
      },
      selectCity() {
        const locationCityCode = this.formSearch.cityId;
        if (locationCityCode) {
          this.locationAreaCodeOptions = this.locationAreaCodeOptionsAll.filter(
            (x) =>
              x.parentId ===
              this.locationCityCodeOptions.find(
                (x) => x.deptId === locationCityCode
              ).deptId
          );
          this.formSearch.countyId = null;
        } else {
          this.locationAreaCodeOptions = [...this.locationAreaCodeOptionsAll];
        }
      },
      queryData(current) {
        this.loading = true;
        current = current || this.pagination.current;
        if (this.time && this.time.length !== 0) {
          this.formSearch.registerStartTime = this.time[0];
          this.formSearch.registerEndTime = this.time[1];
        }
        // getEnterpriseUserPage({
        //   ...this.formSearch,
        //   ...this.pagination,
        //   current
        // }).then((res) => {
        //   const { records, total } = res.data.data;
        //   this.tableData = records;
        //   this.total = total;
        //   this.loading = false;
        // });
      },
      sizeChangeHandle(val) {
        this.pagination.size = val;
        this.queryData();
      },
      currentChangeHandle(val) {
        this.pagination.current = val;
        this.queryData();
      },
      clearData() {
        this.$refs.form.resetFields();
        this.formSearch = {
          nickname: '',
          cityId: '',
          countyId: '',
          registerEndTime: '',
          registerStartTime: ''
        };
        this.time = '';
        this.queryData();
      }
    }
  };
  </script>
  
  <style>
  .flex-p {
    display: flex;
    justify-content: space-between;
  }
  
  </style>
  

展示效果

image.png

优缺点: 封装简单,页面利用插槽直接写入对应代码 每个页面都需要重复定义,比如分页

2. 全部封装起来,包括请求 分页器等

TableColumns.vue

<template>
  <el-table-column
    v-if="!curColumn.type"  
    :key="curColumn.prop || curColumn.label || curColumn.type"
    :label="curColumn.label"
    :prop="curColumn.prop"
    :width="curColumn.width"
    :min-width="curColumn.minWidth"
    :column-key="curColumn.prop"
    :fixed="curColumn.fixed"
    :sortable="curColumn.sortable"
    :sort-method="curColumn.sortMethod"
    :sort-by="curColumn.sortBy"
    :resizable="curColumn.resizable"
    :show-overflow-tooltip="curColumn.showOverflowTooltip"
    :align="curColumn.align"
    :filters="curColumn.filter ? filters : undefined"
    :filter-method="curColumn.filter ? doFilter : undefined"
    :class-name="curColumn.className"
    class="c--table-columns"
  >
    <!-- header -->
    <template
      v-if="curColumn.formItem"
      slot="header"
    >
      <el-input
        v-if="curColumn.formItem.type === 'input'"
        v-model="filterProp"
        :placeholder="curColumn.formItem.placeholder"
      />
      <el-select
        v-if="curColumn.formItem.type === 'selector'"
        v-model="filterProp"
        :placeholder="curColumn.formItem.placeholder"
      >
        <el-option
          v-for="(option, idx) in curColumn.formItem.options"
          :key="idx"
          :value="option.value"
          :label="option.label"
        />
      </el-select>
      <el-date-picker
        v-if="curColumn.formItem.type === 'datePicker'"
        v-model="filterProp"
        :placeholder="curColumn.formItem.placeholder"
        type="datetime"
      />
    </template>
    <!-- body -->
    <template slot-scope="scope">
      
       <!-- input 输入框 -->
       <div v-if="curColumn.canEdit && scope.row.isEditing">
        <el-input type="number" v-model="scope.row[scope.column.property]" ></el-input>
      </div>
       <!-- input 文本域 -->
       <div  v-else-if="curColumn.viewEdit  ">
        <el-input  type="textarea"   maxlength="40" show-word-limit  v-model="scope.row[scope.column.property]"></el-input>
      </div>
      <!-- select 选择框 自评价-->
      <div v-else-if="curColumn.selfEdit && scope.row.isEditing">
        <el-select v-model="scope.row[scope.column.property]" @change="tableSelect(scope.row)" placeholder="">
            <el-option
              v-for="item in selFoptions"
              :key="item.value"
              :label="item.label"
              :value="item.value">
            </el-option>
        </el-select>
      </div>
      <!-- 时间选择器 -->
      <div v-else-if="curColumn.timeEdit && scope.row.isEditing">
         <el-date-picker
            v-model="scope.row[scope.column.property]"
            type="date"
            value-format="yyyy-MM-dd"
            placeholder="选择日期">
          </el-date-picker>
      </div>

      <div
        v-else-if="curColumn.buttons && curColumn.buttons.length > 0"
        class="column-buttons-container"
      >
        <template v-for="(btn, index) in curColumn.buttons">
          <div
            :key="index"
            :class="btn.wrapperClass"
            class="column-btn-wrapper"
          >
            <el-button
              v-if="typeof btn.name === 'function' ? btn.name(scope.row) : btn.name"
              :class="typeof btn.class === 'function' ? btn.class(scope.row) : (btn.class || '')"
              :size="btn.size || 'mini'"
              :type="typeof btn.type === 'function' ? btn.type(scope.row) : (btn.type || 'default')"
              :disabled="typeof btn.disabled === 'function' ? (btn.dishandler ?  btnrowActions(scope.row, btn.dishandler) : btn.disabled(scope.row)) : (btn.disabled || false)"
              class="column-btn-action"
              @click.stop="rowActions(scope.row, btn.handler)"
            >
              {{ typeof btn.name === 'function' ? btn.name(scope.row) : btn.name }}
            </el-button>
          </div>
        </template>
      </div>
      <div
        v-else-if="curColumn.template"
        v-html="renderTemplate(scope.row, scope.column, scope.$index)"
      >
      </div>
      <div v-else>
        {{ renderTemplate(scope.row, scope.column, scope.$index) }}
      </div>
    </template>
    <TableColumns
      v-for="(col, idx) in curColumn.children || []"
      :key="idx"
      :column="col"
      @link="link"
    />
  </el-table-column>
  <el-table-column
    v-else
    :type="curColumn.type"
    :label="curColumn.label"
    :width="curColumn.width"
    :min-width="curColumn.minWidth"
    :column-key="curColumn.prop"
    :fixed="curColumn.fixed"
    :resizable="curColumn.resizable"
    :show-overflow-tooltip="curColumn.showOverflowTooltip"
    :align="curColumn.align"
    :class-name="curColumn.className"
    :reserve-selection="true"
    ref = "cloums"
  />
    <!-- row-key="selected" -->

</template>

<script>
import TableColumns from './TableColumns'

export default {
  name: 'TableColumns',
  components: {
    TableColumns
  },
  props: {
    column: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  data () {
    return {
      defaultColumn: {
        label: undefined,
        prop: undefined,
        type: undefined,
        width: undefined,
        minWidth: undefined,
        fixed: false,
        sortable: false,
        sortBy: undefined,
        resizable: true,
        showOverflowTooltip: true,
    
        align: 'left',
        className: '',
        filter: false,
        sortMethod (a, b) {
          return 0
        },
        formatter (row, column, cellValue, index) {
          return cellValue
        }
      },
      filters: [],
      filterObject: {},
      filterProp: '',
      timer: null,
      selFoptions: [{
          label: '未完成',
          value: 3
        },{
          label: '已完成',
          value: 4
        },{
          label: '未启动',
          value: 1
      }],
    }
  },
  computed: {
    curColumn () {
      return Object.assign({}, this.defaultColumn, this.column)
    }
  },
  watch: {
    column: {
      deep: true,
      handler () {
        this.filterObject = {}
      }
    },
    filterObject: {
      deep: true,
      handler (val) {
        let arr = []
        for (let key in val) {
          arr.push({
            value: key,
            text: val[key]
          })
        }
        this.filters = arr
      }
    },
    filterProp (val) {
      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.$emit('doFilter', val)
        this.timer = null
      }, 60)
    }
  },
  mounted () {
   
  },
  methods: {
    // 下路发生变化的时候
    tableSelect (row) {
        if (row.selfEvaluation === 1) {
            row.taskScore = 0
        } else if  (row.selfEvaluation === 3) {
            row.taskScore = 0.7 * (row.taskFactor * 100)
        } else {
            row.taskScore =  row.taskFactor * 100
        }
    },
    // 数据转换处理以及过滤列表初始化
    initHandler (row, column, cellValue, index) {
      
      let text = this.curColumn.formatter(row, column, cellValue, index) || cellValue
    
      if (!this.filterObject[cellValue]) {
        this.$set(this.filterObject, cellValue, text)
      }
     
      return text
    },
    // 表格内部过滤,不经过后台进行数据请求
    doFilter (value, row, column) {
      let prop = column['property']
      return row[prop] + '' === value + ''
    },
    // 自定义模板渲染
    renderTemplate (row, column, index) {
      let col = this.curColumn
      let val = row[column['property']] || ''
      if (!col.template) return this.initHandler(row, column, val, index)
      return typeof col.template === 'function'
        ? col.template(val)
        : col.template.replace('{value}', this.initHandler(row, column, val, index))
    },
    // 自定义按钮事件
    rowActions (row, handler = function () {}) {
      handler.call(this, row)
    },
    btnrowActions(row, handler = function () {}) {
      return handler.call(this, row)
    },
    // link用于配合layout组件进行跨组件通信
    link (data) {
      this.$emit('link', data)
    }
  }
}
</script>

<style scoped>
.column-buttons-container{
  position: relative;
  display: flex;
  justify-content: space-between;
}
.column-btn-wrapper{
  position: relative;
  flex: 1;
}
.column-btn-action{
  position: relative;
}
.long-column-btn-action{
  position: relative;
  width: 100%;
}
.el-date-editor.el-input, .el-date-editor.el-input__inner {
  width: 150px;
}

</style>

TableCommon.vue

<template>
  <div :id="curOption.id" class="table-common-wrapper c--table-common">
    <div class="table-common-container">
      <div
        v-if="Object.keys(buttons).length > 0"
        :class="{ 'btns-wrapper-absolute': buttons.absolute }"
        class="btns-wrapper"
      >
        <el-button-group v-if="btnRefresh">
          <el-button v-if="buttons.add && !isEditing" @click="add">
            新增
          </el-button>
          <el-button v-if="buttons.update" @click="update"> 修改 </el-button>
          <el-button v-if="buttons.planAdd" @click="planAdd"> 新增 </el-button>
          <el-button v-if="buttons.planSave" @click="planSave">
            保存
          </el-button>
          <el-button v-if="buttons.planSuv" @click="planSuv"> 提交 </el-button>

          <el-button v-if="buttons.delete" @click="rowsDelete">
            删除
          </el-button>
          <el-button v-if="buttons.export" @click="dataExport">
            导出
          </el-button>
          <el-button v-if="buttons.import" @click="dataImport">
            数据引入
          </el-button>
          <el-button v-if="buttons.edit" @click="doEdit()">
            {{ isEditing ? "保存" : "编辑" }}
          </el-button>
          <el-button v-if="isEditing" @click="editCancel()">
            取消编辑
          </el-button>
          <!--仅在数据引用时启用-->
          <el-button v-if="isImporting" @click="selectedConfirm()">
            确认
          </el-button>
          <el-button v-if="isImporting" @click="selectedClear()">
            清除
          </el-button>
          <el-button v-if="isImporting" @click="selectedCancel()">
            取消
          </el-button>
          <el-button
            v-for="(btn, index) in buttons.custom"
            :key="index"
            :class="
              typeof btn.classes === 'function' ? btn.classes(btn) : btn.classes
            "
            :save="elemRender(btn)"
            @click="headerButtonHandler(btn)"
          >
            {{ typeof btn.name === "function" ? btn.name() : btn.name }}
          </el-button>
        </el-button-group>
      </div>
      <div v-if="curOption.title" class="table-common-title">
        {{ curOption.title }}
      </div>
      <div
        ref="tableCommonContent"
        class="table-common-content"
        @click="contextMenuClosed"
      >
        <el-table
          v-if="showTable"
          ref="tableCommon"
          :data="curData"
          :show-header="curOption.showHeader"
          :height="curOption.height"
          :max-height="curOption.maxHeight"
          :fit="curOption.fit"
          :show-summary="curOption.showSummary"
          :border="curOption.border"
          :size="curOption.size"
          :row-class-name="curMethods.rowClassName"
          :row-style="curMethods.rowStyle"
          :cell-class-name="cellClassName"
          :cell-style="curMethods.cellStyle"
          :span-method="curMethods.spanMethod"
          :summary-method="curMethods.summaryMethod"
          @selection-change="selectHandler"
          @select="select"
          @select-all="selectAll"
          @row-click="rowClick"
          @row-contextmenu="contextMenu"
          @row-dblclick="rowDoubleClick"
          @cell-mouse-enter="cellEnter"
          @cell-mouse-leave="cellLeave"
          @current-change="selectCur"
        >
          <template v-for="(column, index) in columns">
            <TableColumns
              :key="index"
              :column="Object.assign({}, { align: curOption.align }, column)"
              @doFilter="doFilter"
              @link="link"
            />
          </template>
        </el-table>
      </div>
      <div v-if="curOption.showPagination" class="pagination-wrapper">
        <el-pagination
          :current-page="curPagination.currentPage"
          :page-sizes="curPagination.pageSizes"
          :page-size="curPagination.pageSize"
          :pager-count="curPagination.count"
          :total="curPagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          background
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </div>
    <el-card
      v-if="curOption.contextMenu"
      v-show="showContextMenu"
      ref="cmenu"
      :style="{ left: position.x + 'px', top: position.y + 'px' }"
      class="context-menu-wrapper"
    >
      <div slot="header" class="context-menu-header">
        <span>功能菜单</span>
        <i
          class="el-icon-close context-menu-close"
          @click="contextMenuClosed"
        />
      </div>
      <div class="context-menu-list">
        <el-button
          :disabled="!buttons.delete"
          size="small"
          class="context-menu-btn"
          @click="contextMenuHandler(rowsDelete)"
        >
          删除
        </el-button>
      </div>
      <div class="context-menu-list">
        <el-button
          :disabled="!buttons.edit"
          size="small"
          class="context-menu-btn"
          @click="contextMenuHandler(doEdit)"
        >
          {{ isEditing ? "保存" : "编辑" }}
        </el-button>
      </div>
      <div class="context-menu-list">
        <el-button
          :disabled="!buttons.update"
          size="small"
          class="context-menu-btn"
          @click="contextMenuHandler(update)"
        >
          修改
        </el-button>
      </div>
      <div
        v-for="(item, index) in curOption.contextMenu"
        :key="index"
        class="context-menu-list"
      >
        <el-button
          size="small"
          class="context-menu-btn"
          @click="contextMenuHandler(item.handler)"
        >
          {{ item.name }}
        </el-button>
      </div>
    </el-card>
  </div>
</template>

<script>
import TableColumns from "./TableColumns";
import request from "@/util/request";

export default {
  name: "TableCommon",
  components: {
    TableColumns,
  },
  props: {
    option: {
      type: Object,
      default: () => {
        return {};
      },
    },
    api: {
      type: Object,
      default: () => {
        return {};
      },
    },
    methods: {
      type: Object,
      default: () => {
        return {};
      },
    },
    columns: {
      type: Array,
      default: () => [],
    },
    buttons: {
      type: Object,
      default: () => {
        return {};
      },
    },
    pagination: {
      type: Object,
      default: () => {
        return {};
      },
    },
    importMode: {
      type: Boolean,
      default: undefined,
    },
    defaultEedit: {
      type: Function,
      default: () => {
        return {};
      },
    },
  },
  data() {
    return {
      // loading: true,
      curData:[],
      dataList: [],
      showTable: false,
      btnRefresh: true,
      tableData: [],
      curRow: null,
      defaultOption: {
        id: "",
        title: "",
        height: "100%",
        maxHeight: undefined,
        border: true,
        size: "mini",
        fit: true,
        showSummary: false,
        showHeader: true,
        align: "left",
        static: false,
        contextMenu: null,
        showPagination: true,
        actiqeCellIndexBefore: 0,
      },
      defaultMethods: {
        dataHandler(data) {
          return data;
        },
        spanMethod({ row, column, rowIndex, columnIndex }) {
          return { rowspan: 1, colspan: 1 };
        },
        summaryMethod({ columns, data }) {
          let arr = [];
          return arr;
        },
        rowClassName({ row, rowIndex }) {
          return "";
        },
        rowStyle({ row, rowIndex }) {
          return {};
        },
        cellClassName({ row, column, rowIndex, columnIndex }) {
          return "";
        },
        cellStyle({ row, column, rowIndex, columnIndex }) {
          return {};
        },
        rowClick(row, column, index) {},
        rowContextmenu(row, column, index) {},
        rowDoubleClick(row, column, index) {},
      },
      defaultPagination: {
        currentPage: 1,
        pageSizes: [5, 10, 20, 30, 40, 50, 100, 200, 500, 100000],
        pageSize: 10,
        total: 0,
        count: 7,
      },
      isRequesting: false,
      selection: [],
      selectionOrigin: [],
      isEditing: false,
      showContextMenu: false,
      position: {
        x: 0,
        y: 0,
      },
      contextMenuRow: null,
      contextMenuColumn: null,
      contextMenuRowIndex: -1,
      formItemValue: "",
      isImporting: this.importMode || false,
    };
  },

  computed: {
    curOption() {
      return Object.assign({}, this.defaultOption, this.option);
    },
    curMethods() {
      return Object.assign({}, this.defaultMethods, this.methods);
    },
    // curData() {
    //   if (this.formItemValue === "") return this.tableData;
    //   let arr = [];
    //   this.tableData.forEach((item) => {
    //     for (let key in item) {
    //       let val = "";
    //       this.columns.forEach((col) => {
    //         if (!col.prop || col.prop !== key) return;
    //         val =
    //           typeof col.formatter === "function"
    //             ? col.formatter(null, null, item[key]) + ""
    //             : typeof col.template === "function"
    //             ? col.template(item[key]) + ""
    //             : item[key] + "";
    //       });
    //       if (this.formItemValue !== "" && !val.includes(this.formItemValue))
    //         continue;
    //       arr.push(item);
    //       break;
    //     }
    //   });
      
    //   console.log(this.dataList,'curData');
    //   // return arr;
    //   return this.dataList
    // },
    curPagination() {
      return Object.assign({}, this.defaultPagination, this.pagination);
    },
  },
  watch: {
    api: {
      deep: true,
      handler() {
        // this.defaultPagination.currentPage = 1
        this.doRequest();
      },
    },
    columns: {
      deep: true,
      handler() {
        this.showTable = false;
        this.$nextTick(() => {
          this.showTable = true;
        });
      },
    },
    importMode(val) {
      this.isImporting = val;
    },
    "$store.state.doTableRefresh"(val) {
      if (!val) return false;
      // this.$store.commit('refreshTable', false)
      this.$store.dispatch("changeTale", false);
      this.showTable = false;
      this.$nextTick(() => {
        this.showTable = true;
        this.doRequest("forceRefresh");
      });
    },
    "$store.state.refreshTable"(val) {
      if (!val) return false;
      this.doRequest();
      // table 表格更新
      this.$store.dispatch("changeTale", false);
      // this.$store.commit('refreshTable', false)
      // this.showTable = false
      // this.$nextTick(() => {
      //   this.showTable = true
      //   this.doRequest('forceRefresh')
      // })
    },
  },
  created() {
    sessionStorage.setItem("entery", "0");
    this.doRequest();
  },
  mounted() {
    setTimeout(() => {
      let height = this.$refs.tableCommonContent.clientHeight;
      height = height === 0 ? "auto" : height + "px";
      this.defaultOption = Object.assign({}, this.defaultOption, {
        height,
      });
      this.showTable = true;
      // 获取当前模式
      let query = this.$route.query;
      if (query.mode && query.mode === "import") {
        this.isImporting =
          this.importMode !== undefined ? this.importMode : true;
        return false;
      }
    }, 0);
  },
  methods: {
    // 请求数据
    doRequest(type = "") {
      console.log(1);
      // 处于编辑中或请求中时,不进行下一次请求操作
      if (this.isEditing) return false;
      console.log(2);
      if (this.isRequesting) return false;
      console.log(3);
      this.isRequesting = true;
      let api = this.api || {};
      // 获取分页信息
      let curPagination = this.curPagination;
      let size = curPagination.pageSize;
      size = !Number.isFinite(size) ? 999999999 : size;
      let page = curPagination.pageNum;

      let headers =
        typeof api.headers === "function"
          ? api.headers(this)
          : api.headers || {};
      api.url = api.url || api["dispatch-url"] || headers["dispatch-url"] || "";
      api.method =
        api.method ||
        api["dispatch-method"] ||
        headers["dispatch-method"] ||
        "get";
      api.name =
        api.name || api["data-key"] || headers["data-key"] || "get-message";
      api.prefix = api.prefix || headers.prefix || "";
      if (!api.url || this.curOption.static) {
        this.isRequesting = false;
        api.mockData = api.mockData || [];
        // 更新分页总数
        let total = api.mockData.length;
        this.$set(this.defaultPagination, "total", total);
        let start = (page - 1) * size;
        this.tableData =
          api.mockData.slice(start, Math.min(size + start, total)) || [];
        return false;
      }
      // 如果params是个函数,表示需要传入当前的vue对象进行计算返回一个新对象
      let params =
        typeof api.params === "function" ? api.params(this) : api.params;
      let obj =
        api.method.toLowerCase() === "get"
          ? {
              url: api.url,
              method: api.method,
              headers: api.headers || {},
              params: { ...params, pageSize: size, pageNum: page },
            }
          : {
              url: api.url,
              method: api.method || "post",
              headers: api.headers || {},
              data: { ...params, pageSize: size, pageNum: page },
            };
      let arr = api.dataPath.split(".");
      // websocket的情况
      request({
        url: api.url,
        method: api.method,
        params: params,
      }).then((res) => {
        console.log(res,'res-request');
        this.dataList = res.tableData;
        this.curData = res.tableData;
        this.dataHandler(res, arr, type);
      });
    },
    // 返回数据处理
    dataHandler(res, arr, type) {
      let data = res || {};
      for (let [i, len] = [0, arr.length]; i < len; i++) {
        if (!data[arr[i]]) break;
        data = data[arr[i]];
      }
      data = this.curMethods.dataHandler(data, this, type) || data;

      this.isRequesting = false;
      this.tableData = data;
      // 更新总数
      this.$set(
        this.defaultPagination,
        "total",
        res.total ||
          (res.data && res.data.total) ||
          (res.data.paginationInfo && res.data.paginationInfo.total) ||
          data.length
      );
      this.showTable = false;
      this.$nextTick(() => {
        this.showTable = true;
        // table 表格更新
        this.$store.commit("refreshTable", true);
      });
    },
    // 选择
    selectHandler(selection) {
      console.log(selection, "selectionselection");
      this.selection = selection || [];
    },
    select(selection, row) {
      if (!this.curOption.selection) return false;
      if (selection.length > 1) {
        let del_row = selection.shift();
        this.$refs.tableCommon.toggleRowSelection(del_row, false);
      }
    },
    selectAll(selection) {
      if (!this.curOption.selection) return false;
      if (selection.length > 1) {
        selection.length = 1;
      }
    },
    selectCur(row, old) {
      this.$refs.tableCommon.toggleRowSelection(row);
      if (this.curOption.selection) {
        this.$refs.tableCommon.toggleRowSelection(old, false);
      }
    },
    // 编辑状态, 只有被选中的项能被编辑
    doEdit() {
      this.contextMenuClosed();
      if (this.isEditing) {
        // 保存并退出
        this.curMethods.saveEdit(this);
        return false;
      }
      if (this.selection.length === 0) {
        this.$alert("请至少选择一条数据进行编辑");
        return false;
      }
      this.isEditing = true;
      // 表格选中位置匹配
      let selection = [];
      this._tableHandler((item, dataItem) => {
        if (JSON.stringify(item) === JSON.stringify(dataItem)) {
          selection.push(JSON.parse(JSON.stringify(item)));
          this.$set(dataItem, "isEditing", true);
        }
      });
      // 保存被选中行编辑前的原始状态
      this.selectionOrigin = selection;
    },
    // 退出编辑状态
    editCancel(save = false) {
      let selection = [];
      // 退出编辑状态
      this.isEditing = false;
      // 进行前置值判断
      let noNeedCancelEdit = false;
      this.tableData.forEach((dataItem) => {
        if (dataItem.isEditing) this.$set(dataItem, "isEditing", false);
      });
      this.doRequest();
      
    },
    // 处理编辑状态下的表格和选择循环
    _tableHandler(handler = function () {}, selection) {
      selection = selection || this.selection;
      for (let [i, len] = [0, this.tableData.length]; i < len; i++) {
        let dataItem = this.tableData[i];
        selection.forEach((item, index) => {
          handler(item, dataItem, i, index);
        });
      }
    },
    // 新增事件上报
    add() {
      // 判断是否其他操作在进行中
      // if (!this.checkIsPassing()) return false
      this.$emit("add");
      console.log(123);
    },
    // 修改事件上报
    update() {
      // 判断是否其他操作在进行中
      this.contextMenuClosed();
      if (!this.checkIsPassing(true, true)) return false;
      this.$emit("update", this.selection);
    },

    planAdd() {
      this.$emit("planBtn", { method: "add" });
    },
    planSave() {
      this.$emit("planBtn", { method: "save" });
    },
    planSuv() {
      this.$emit("planBtn", { method: "suv" });
    },
    // 删除事件上报
    rowsDelete() {
      // 判断是否其他操作在进行中
      this.contextMenuClosed();
      if (!this.checkIsPassing(true)) return false;
      this.$emit("delete", this.selection);
    },
    // 导出表格
    dataExport() {
      this.$emit("dataExport", this.selection);
    },
    // 引入数据
    dataImport() {
      this.$emit("dataImport");
    },
    // 自定义按钮处理
    headerButtonHandler(button) {
      // 简单判断
      if (typeof button !== "object") return;
      if (!this.checkIsPassing(button.needSelection, button.onlyOne))
        return false;
      button.handler.call(this, this.selection, button);
    },
    // 检查是否通过检测
    checkIsPassing(needSelection = false, onlyOne = false) {
      if (this.isEditing) {
        this.$notify("当前正在编辑中,请退出编辑再继续操作");
        return false;
      }
      if (this.isRequesting) {
        this.$notify("当前正在数据请求中,请等请求完成后再继续操作");
        return false;
      }
      // if (needSelection && this.selection.length === 0) {
      //   this.$alert('请至少选择一条数据再进行操作')
      //   return false
      // }
      // if (needSelection && onlyOne && this.selection.length !== 1) {
      //   this.$alert('最多同时只能选择一条数据再进行操作')
      //   return false
      // }
      return true;
    },
    // 单元格class
    cellClassName(data) {
      return this.curMethods.cellClassName
        ? this.curMethods.cellClassName.call(this, data)
        : "";
    },
    // 右键
    contextMenu(row, column, event) {
      event.preventDefault();
      event.stopPropagation();
      this.showContextMenu = true;
      this.$nextTick(() => {
        let curMethods = this.curMethods || {};
        let el = this.$refs.cmenu.$el;
        let cw = el.clientWidth + 10;
        let ch = el.clientHeight + 10;
        let [x, y] = [event.clientX, event.clientY];
        if (x + cw >= window.innerWidth) x = window.innerWidth - cw;
        if (y + ch >= window.innerHeight) y = window.innerHeight - ch;
        curMethods.rowContextmenu &&
          curMethods.rowContextmenu.call(this, row, column, event);
        this.position = {
          x,
          y,
        };
        this.contextMenuRow = row;
        this.contextMenuColumn = column;
        this.tableData.forEach((item, index) => {
          if (JSON.stringify(item) === JSON.stringify(row)) {
            this.contextMenuRowIndex = index;
          }
        });
      });
    },
    // 左键
    rowClick(row, column, event) {
      let curMethods = this.curMethods || {};
      curMethods.rowClick && curMethods.rowClick.call(this, row, column, event);
    },
    // 左键双击
    rowDoubleClick(row, column, event) {
      let curMethods = this.curMethods || {};
      curMethods.rowDoubleClick &&
        curMethods.rowDoubleClick.call(this, row, column, event);
    },
    // 单元格鼠标移入
    cellEnter(row, column, cell, event) {
      // let curMethods = this.curMethods || {}
      // this.curRow = this.$extend(true, {}, row)
      // curMethods.cellEnter && curMethods.cellEnter.call(this, row, column, cell, event)
    },
    // 单元格鼠标移出
    cellLeave(row, column, cell, event) {
      let curMethods = this.curMethods || {};
      this.curRow = null;
      curMethods.cellLeave &&
        curMethods.cellLeave.call(this, row, column, cell, event);
    },
    // 关闭右键菜单
    contextMenuClosed() {
      this.showContextMenu = false;
    },
    // 自定义右键处理
    contextMenuHandler(handler = function () {}) {
      this.$refs.tableCommon.clearSelection();
      this.$refs.tableCommon.toggleRowSelection(this.contextMenuRow, true);
      this.contextMenuClosed();
      handler.call(
        this,
        this.contextMenuRow,
        this.contextMenuColumn,
        this.contextMenuRowIndex,
        this.tableData
      );
    },
    // 表头表单项内部过滤
    doFilter(data) {
      // 当表格有其他操作时进行查询阻止
      if (this.isEditing || this.isRequesting) return false;
      this.formItemValue = data;
    },
    // 分页跨度尺寸变化
    handleSizeChange(val) {
      val = Number.isNaN(val) ? Infinity : val;
      this.$set(this.defaultPagination, "pageSize", val);
      if (this.defaultPagination.currentPage > 1) {
        this.$set(this.defaultPagination, "currentPage", 1);
      }
      this.$emit("pageSize", val);
      this.doRequest();
    },
    // 当前页面变化
    handleCurrentChange(val) {
      console.log(val,'handleCurrentChange');
      this.$set(this.defaultPagination, "currentPage", val);
      // 提交父组件页面变化
      this.$emit("pageChange", val);
      this.doRequest();
    },
    // 确认并上传选择的数据
    selectedConfirm() {
      if (this.selection.length === 0) {
        this.$comfirm("您还没有选择数据,是否继续执行").then(() => {
          this.importedComplete();
        });
        return false;
      }
      this.importedComplete();
    },
    // 清空数据选择
    selectedClear() {
      this.$refs.tableCommon.clearSelection();
    },
    // 取消数据选择
    selectedCancel() {
      this.importedComplete([]);
    },
    importedComplete(selection) {
      selection = selection || this.selection;
      // 如果不存在嵌入页面的情况
      if (window === window.parent) {
        this.$emit("imported", selection);
        return false;
      }
      // 调用父页面的getSelectedData事件
      window.parent._getSelectedData &&
        window.parent._getSelectedData(selection);
    },
    // link用于配合layout组件进行跨组件通信
    link(data) {
      this.$emit("link", data);
    },
    // 初始化部分不易获取的元素
    elemRender(tar) {
      tar && tar.render && tar.render(tar, this);
    },
    // 更新右上角的按钮
    refreshBtns() {
      this.btnRefresh = false;
      this.$nextTick(() => {
        this.btnRefresh = true;
      });
    },
  },
};
</script>

<style scoped>
.table-common-wrapper {
  position: relative;
  height: 100%;
  box-sizing: border-box;
}

.table-common-container {
  position: relative;
  height: 100%;
  display: flex;
  flex-wrap: nowrap;
  flex-direction: column;
}

.table-common-content {
  position: relative;
  height: auto;
  flex: 1;
}

.pagination-wrapper {
  position: relative;
  height: auto;
  padding: 8px 4px;
  box-sizing: border-box;
  border-bottom: 1px solid #ebeef5;
  border-right: 1px solid #ebeef5;
  border-left: 1px solid #ebeef5;
}

.table-common-title {
  position: relative;
  height: auto;
  padding: var(--extra-space) 0;
  text-align: center;
  font-size: 1.2em;
  color: var(--table-default-color);
  border: 1px solid var(--table-default-border);
  border-bottom: none;
  background-color: var(--table-default-background-color);
}

.btns-wrapper {
  position: relative;
  height: auto;
  box-sizing: border-box;
  text-align: right;
  padding-bottom: 10px;
}

.btns-wrapper .el-button {
  font-size: 12px;
  border-radius: 3px;
  padding: 7px 15px;
}

.context-menu-header {
  position: relative;
  display: flex;
  align-content: space-between;
  justify-content: center;
}

.context-menu-header > span {
  flex: 1;
}

.context-menu-close:hover {
  cursor: pointer;
  color: var(--table-default-border);
}

.btns-wrapper-absolute {
  position: absolute;
  width: 100%;
  transform: translateY(-100%);
}

.context-menu-wrapper {
  position: fixed;
  z-index: 10;
}

.context-menu-list {
  width: 160px;
  padding-bottom: 6px;
}

.context-menu-btn {
  width: 100%;
}

.table-common-content /deep/ .cell-active {
  background-color: var(--table-default-hover);
}

.table-common-content /deep/ .cell-canClick {
  cursor: pointer;
}
</style>

页面使用情况

<template>
  <div>
    <TableCommon
      ref="table"
      :api="api"
      :option="Keyoption"
      :methods="methods"
      :columns="plancolumns"
      :buttons="Combuttons"
      @link="link"
      @pageChange="pageChange"
      @pageSize="pageSize"
     
    />
  </div>
</template>

<script>
import TableCommon from "@/components/TableCommon/TableCommon.vue";
let hashTable = {};
let tempHashTable = {};
let tempHashTables = {};
export default {
  data() {
    return {
      Combuttons: {
        add: true,
        export: true,
      },
      Keyoption: {
        align: "center",
        id: "classTable",
        showPagination: true,
      },
      methods: {
        cellStyle({ row, column }) {},
        rowClick(row, column) {},
        dataHandler(res) {
          hashTable = {};
          tempHashTable = {};
          for (let [i, len] = [0, res.length]; i < len; i++) {
            let item = res[i];
            // 建立哈希表用于表格行合并
            if (!hashTable[item.departmentName])
              hashTable[item.departmentName] = 0;
            hashTable[item.departmentName]++;
          }
          return res;
        },
      },
      plancolumns: [
        {
          type: "index",
          label: "序号",
        },
        {
          label: "部门",
          prop: "departmentName",
          width: "130",
        },
        {
          label: "任务类型",
          prop: "taskType",
          width: "130",
          formatter(row, column, cellValue) {
            if (cellValue * 1 === 1) {
              return "公司级";
            } else if (cellValue * 1 === 3) {
              return "部门级";
            }
          },
        },
        {
          label: "重点工作任务名称",
          prop: "workTaskType",
          width: "270",
        },
        {
          label: "牵头部门",
          prop: "responsibleDepartment",
          width: "130",
        },
        {
          label: "任务来源",
          prop: "taskSource",
          width: "130",
        },
        {
          label: "预计成果",
          prop: "expectedResults",
          width: "130",
          formatter(row, column, cellValue) {
            if (cellValue * 1 === 1) {
              return "项目完工";
            } else if (cellValue * 1 === 2) {
              return "里程碑节点";
            }
          },
        },
        {
          label: "任务系数",
          prop: "taskFactor",
          width: "130",
        },
        {
          label: "计划完成时间",
          prop: "planCompleteTime",
          width: "130",
        },
        {
          label: "任务描述",
          prop: "taskDesc",
        },
      ],
      api: {
        url: "/business/declare/detailHome",
        method: "get",
        dataPath: "data.list",
        params($comp) {
          return {
            pageNum: 1,
            pageSize: 10,
            taskType: "2",
          };
        },
      },
    };
  },
  components: {
    TableCommon,
  },
  methods: {
    dataExport() {
      console.log(123);
      let year = this.$route.query.month.replace("年", "");
      year = year.replace("月", "");
      let classId = "94b780f57d5d8ef5017db86d366f5fda";
      let obj = {
        month: year,
        organization: classId,
      };
    },
    pageChange(val) {
      var obj = {
        pageNum: val,
        pageSize: this.limit ? this.limit : 10,
        taskType: "2",
      };
      this.api.params = obj;
    },
    pageSize(val) {
      this.limit = val;
      var obj = {
        pageNum: 1,
        pageSize: val,
        taskType: "2",
      };
      this.api.params = obj;
    },
    link(data) {
      if (data.method === "drop") {
        let obj = {};
        obj.id = data.id;
        obj.isModify = "1";
        Putask(obj).then((res) => {
          this.$refs.table.doRequest();
        });
        console.log(data);
      } else if (data.method === "upgrade") {
        let up = {};
        up.id = data.id;
        up.isModify = "3";
        Putask(up).then((res) => {
          this.$refs.table.doRequest();
        });
      }
    },
  },
  
};
</script>

<style lang="scss" scoped>
</style>

展示效果 image.png

优缺点: 使用的时候简单,页面冗余少 封装难度大,并且在返回数据不是想要的情况下,处理起来比较麻烦