记录一次技术选型调研不彻底所带来的悲催故事

感慨

在开始前,我想提个问题给伙计们!

你们认为作为一个开发者在开发项目的过程中最不幸、最悲催的事情是什么?

欢迎伙计们在评论区里写出你的不幸和悲催~

背景

他么的,这是一个悲催的故事~

项目是针对客户的生产需求所定制开发的项目,具体业务细节由于保密原因就不详细叙述了,下面只说明主要需求。

硬性需求

有一硬性需求就是:

客户方面每天都有大量的生产数据产生,需要每天将生产数据统一的进行整理、汇总、填报进入系统,系统会根据基础生产数据进行计算、统计等。

因为涉及的客户部门比较多,抛开里面有很复杂的权限关系不谈,对每个部门来说填报的数据以及要求的表格样式都不一样,各部门分工合作互不干涉。

那么伙计们应该已经清楚了,有这一硬性需求的存在,意味着最主要的基础功能之一就是数据填报。

数据填报录入的形式有很多种:比如Form表单、文件导入、可编辑表格等。

我们首先能想到的就是Form表单,但是也很快被Pass掉,因为一次填报的数据太多,用户体验以及操作起来都太麻烦。

文件导入的方式确实可以很快速的录入数据,但是有一个很大的弊端,就是需要定制模板,而且涉及到各部门的各个功能模块又太多,再者说让用户自己在本地先将数据录入到文件,再打开系统导入进去,那么这个系统也就失去了它的意义了。

所以对于大范围,大体量的数据填报,最好的方案是使用一个是趋近于Excel的可编辑表格来针对不同的功能模块进行定制,(况且,客户在没有这一系统的情况下,都是使用Excel来记录数据的),将本地填写Excel的形式搬移到系统上来,这样用户体验比较好!

可编辑表格组件选型

  • Ag Grid

    传送门:https://www.ag-grid.com/

    AG Grid现在是世界先进的现代Javascript应用程序数据网格之一。它诞生于Angular世界,但它现在与框架完全无关——它不依赖于任何框架。

    AG Grid 支持 Angular、React、JavaScript 和 Vue。

    AG Grid 表格组件性能卓越,功能强大,没有第三方依赖。它的官网上赫然写着「The Best JavaScript Grid in the World」,这可不是吹牛,AG Grid 绝对做到了业界顶级,可与 Excel 一战。它的统计图表功能同时兼并图标和表格的重量级功能,图表即表格,表格即图表,单只是想想就知道这个功能有多复杂与多强大。

    AG Grid 每月下载居然高达超过 60 万次,这不是其他表格组件能比的量,世界五百强企业有一半在使用它。

    而且,它还有社区版(免费版)提供。

    在这里插入图片描述

  • Ant Design ProComponents

    传送门:https://procomponents.ant.design/components/editable-table

    官网介绍:

    (1)简单易用 在 Ant Design 上进行了自己的封装,更加易用

    (2)与 Ant Design 设计体系一脉相承,无缝对接 antd 项目,如果原先的基础框架是Ant,那挺好的。

    (3)提供完备的国际化,与 Ant Design 体系打通,无需多余配置

    (4)样式风格与 antd 一脉相承,无需魔改,浑然天成。默认好用的主题系统

    (5)更少的代码,更少的 Bug,更多的功能

    (6)使用 TypeScript 开发,提供完整的类型定义文件,无需频繁打开官网,但是如果不太懂TypeScript就另当别论了

在这里插入图片描述

  • Vue-EasyTable

    传送门:http://huangshuwei.gitee.io/vue-easytable/#/en/demo

    基于vue2.x的灵活表格组件

    2021年初作者对它进行了一次重大重构升级,UI 也进行了大更新,很漂亮

    支持30万行数据通过虚拟滚动显示 永远免费。

在这里插入图片描述

最终选择

团队最终选择了Ag-Grid,原因有以下几种:

(1)具有社区版;

(2)官网表格可以做得很炫酷,功能强大,考虑之后开发的各种表格编辑的复杂场景功能得足够;

(3)编辑体验和Excel几乎没有区别,编辑体验很流畅;

(4)世界500强都在用的网格组件,应该有强有力的公司支撑,说明更新迭代以及社区生态应该挺完善;

此处省略5000字…

说了这么多原因,但是最主要的还是第一个和第三个,其他的都没有认真调研,只是浅显的了解,看到能满足现在的填报需求就直接用了,这也是不幸和悲催的开始!

悲催故事

P1 他们心中的她

就这样,团队伙伴们就开始了与她(指Ag-Grid-Community 社区版网格)一同上演了一场的情深深泪蒙蒙。

294-1F621151922.jpg

在今后的每个元气满满的日子里,团队的伙伴都乐此不疲的使用着她进行开发各种功能模块的可编辑表格,被开发的可编辑表格过程是如此的丝滑,用户体验是如此的情迷,他们都在一次次的确认眼神中认定了她,因为他们都被她散发的魅力所深深的吸引和折服。

是的,她美得让人陶醉,让人心甘。团队伙伴们一天一天不知疲倦的去追寻着她,无论她有什么问题,伙伴们都想尽各种办法去解决,为了她能够在客户面前尽力展现出她独有的美,伙伴们都选择无条件的包容,无条件的往前。

而我一个后面才知道她并与其共事的人,只能一直默默的熟悉她的一切,慢慢的我也开始被她所吸引,常常为她的美独自买醉。

熟悉之后,不管各位信不信,作为团队的一员,我和我的团队伙伴们一直秉持着情比金坚,海枯石烂的誓言共苦同甘于她。我们是一群感情专一的家伙们。不信?您好好想想一群不问世事一直舔的狗子们能有什么浪荡心思呢?

这种无微不至的关怀和支持一直到所有功能开发完毕,系统正式上线运行,并且项目的团队伙伴都撤了。我心里偷偷的窃喜,我终于可以独自占有她了,她的美现在只对我一个人绽放。但是直到那一天…

P2 对她的质疑

是!就是那一天的那一刻2023年XX月XX日 XX时XX分XX秒XX毫秒,有一个人他出现了,他用质疑的口气对她说:“你真美,其他地方都很好,但你有复制的功能吗?”。一直对她如痴如醉的我一听,竟敢质疑我的她!这绝不允许!所以我斩钉截铁的替她答到:“肯定可以复制啊!”。但我很快被打脸了,我发现线上的她好像连选中都选中不了,更他么别说复制了!

经过一番的查阅资料后,我发现复制功能她是有的,但是需要——Ag-Grid-Enterprise 企业版网格。社区版的被限制了。

传送门:https://www.ag-grid.com/vue-data-grid/licensing/

image.png

P3 解决质疑的三种方案

这就面临着如何解决这个复制的需求问题?

三种方案:

  • (1)This is China,you Know
  • (2)直接购买
  • (3)寻找可代替网格组件

以上三种方案,前两种是优先考虑的。因为系统的基础功能就是表格编辑填报,第三种如果要进行替换组件,几乎等于重构整个系统,这损耗的人力和财力无可估计!

P4 第一种方案实施

只找到了JS版本的,测试了一下确实可以。

image (1).png

但是没有找到Vue版本的,我尝试集成进去但是还是报License Key的警告,并且还会有版权隐患,所以果断放弃!

image (2).png

P5 第二种方案实施

购买Ag-Grid-Enterprise 企业版网格。哎呀洒洒水拉~也就是给她买个首饰啥的,哄一下,心里想着这能有多少钱啊,没问题哥不差钱啊!

我赶紧查询了一下价格,爷来了~

image (3).png

单个应用版本才1000块钱啊,小意思的啦~ 不对!!! $美元?

image (4).png

我嘞个去!真贵啊!但是她是我的那个她啊!钱财乃身外之物,我看钱财如粪土~(嘻嘻😂不是我出钱)我赶紧报告团队领导,看看是否可以购买!

公司的宏图展望和团队领导的大气磅礴真的是无法企及,唯有仰望!得到回复:能公对公就可以买!

doutub_img.png

正当我要为公司和团队领导的霸气所折服的时候,我突然发现:我的那个她好像是外国的~不清楚具体需要怎么汇款,但是好像挺麻烦的😒。

联系国内代理商

为了解决这个公对公付款的问题,我尝试寻找是否在国内有Ag-Grid的代理商,还真有。官网有联系电话,伙计们感兴趣可以咨询下,官网地址传送门:https://www.ag-grid.cn/

我联系代理的结果是:

大概意思是:如果你想使用Ag-Grid-Enterprise 企业版网格,需要购买两种许可证: 开发许可证和部署许可证

一个应用必须包含一个开发许可证和一个部署许可证

也就是他么的需要花两份钱~

价格的话:

单一应用开发许可证+部署许可证 大概一万七

多应用开发许可证+部署许可证 大概一万九

特别注意:就算买了多应用开发许可证+部署许可证之后,开发是可以任意开发了,但是如果项目不同的话,还需要再单独购买部署许可证,大概六千多

  • 单一应用程序开发许可证

许可一个为内部使用而开发的应用程序永久嵌入AG网格企业。

包括新版本的1年订阅、支持和维护。

对于面向客户的应用程序,您还需要部署许可证附加组件。

所有在应用程序上工作的并发、前端JavaScript开发人员都需要添加到许可证计数中,而不仅仅是那些使用AG Grid的开发人员。

“单一应用程序开发许可证”计数内的开发人员是未命名的,只要未超过许可证总数。

单一应用程序开发许可证绑定到一个应用程序名称,不能在其他应用程序上重复使用。

  • 多应用程序开发许可证

许可无限数量的应用程序,为内部使用而开发,以永久嵌入AG网格企业。

包括新版本的1年订阅、支持和维护。

对于面向客户的应用程序,您还需要部署许可证附加组件。

所有在授权应用程序中工作的并发前端JavaScript开发人员都需要添加到许可证计数中,而不仅仅是那些使用AG Grid的开发人员。

“多应用程序开发许可证”计数内的开发人员是未命名的,只要未超过许可证总数。

  • 部署许可证加载项

允许获得许可的开发人员永久为一个生产环境中的一个应用程序再许可AG Grid。包括新版本的1年订阅、支持和维护。仅提供开发人员许可证。

部署许可附加模块允许您的组织之外的个人(例如您的客户)可以使用项目(子许可)。

一个部署许可附加模块为一个项目覆盖一个生产环境。

只有生产环境需要许可。所有其他环境(如开发、测试、预生产)不需要许可证。

我们不对每台服务器收费。一个应用程序安装中包含多个服务器的集群被视为一个部署,需要一个部署许可证。只要集群中的应用程序实例是彼此的副本,并且服务器仅提供负载平衡和故障转移,则情况就是如此。 生产故障切换部署不需要单独获得许可。它们被视为整个应用程序生产部署的一部分。

多租户部署,其中一个应用程序实例通过不同的URL为许多客户提供服务,被视为一个部署,因为每个租户都由同一应用程序实例提供服务。

同一应用程序的不同实例(其中这些实例不是用于故障转移或负载平衡的集群的一部分)被视为独立部署,并且每个应用程序实例都需要部署许可证。

将应用程序部署到云服务(如AWS或Docker)需要一个部署许可证,无论云应用程序为该应用程序的单个实例生成多少个虚拟容器或服务器。

联系Ag-Grid官方验证

我验证了其代理收费方式,确实和官方一样,这好像是对代理商规定的,具体邮件就给伙计们展示了~有疑问可以自己去咨询哈

这贵了一倍不止!我再给领导汇报,得到结果不出所料:先找找其他方式试试看看~🤦‍♂️🤦‍♂️🤦‍♂️

P6 第三种方案实施

我的个肾那~ 剩下的方案无非就是找组件替换呗,我情不自禁的一手拿着刀,一手操作着鼠标默默的数着需要替换的表格功能模块数量,50多个。

那个谁,别拦我!

7305142_2015110916361218891.jpg

等我冷静下来,其他两个方案都被排除了,于是乎我认真思考了一下第三种方案:

(1)系统已经正式上线了,客户对于Ag-Grid的表格的使用已经熟悉了,再换组件是否合理,客户是否能接受;

(2)需替换的功能模块数量多,需要花费精力太大,最悲催的是这个项目几乎他么的就我自己在维护支持了;

(3)有没有一种方式,可以几乎做到静默替换;

(4)假如做到静默替换了,但是本质上不同的组件,肯定会有差异,客户能否接受;

(5)客户之后是否会提出可以任意选择某列某行某单元格进行复制,而不是只能整行复制;

先说(1)、(2)点,实际上就是沟通的问题,经过有效沟通,只要填报的形式和之前一样就可以。

但是说回(3)、(4)点,如果直接全部替换组件的话,50多个表格太他么费劲了!

(5)点,你杀了我吧,如果要有这种需求,可能就真的需要购买了Ag-Grid-Enterprise 企业版了!

经过的一番折腾,总的来说,前期对于Ag-Grid网格的技术选型调研很不彻底,考虑不周全,虽然已经知道会收费,但没有深入了解到后续的收费方式以及具体价格。

如果当时调研清楚后,可以将其计入项目预算中。什么?现在再给客户要钱?别逗了大哥,项目都已经正式验收上线运营了,早干啥去了~

不幸中的万幸

所以,就陷入了项目刚正式上线,就要替换表格组件,相当于重构系统的尴尬境地,我吐了~😢😢

但不幸的万幸在使用Ag-Grid网格组件时,并没有直接使用,而是使用Vue又封装了一层,这就让我实施第三种方案有了些许欣慰,我当时真是太聪明了~

这给了我一点可以不用大面积重构系统的希望,好吧但不大。

最终方案

最终采用的方案是:

在用户编辑时还是使用原来的Ag-Grid网格,仅查看时则使用自己封装的Element-UI的表格组件——ElCommonTable。因为Ag-Grid做了限制,不能复制,但Element-UI所带的表格组件没有这个限制,可以复制。

这里有可能伙伴们会问,为什么不引入Ant ProComponents的可编辑表格呢?

主要是考虑系统已经使用了一套Vue组件框架了就是Element-Ui,如果再引入一套Ant框架后续可能不好维护。

在切换的时候,我想静默替换,尽量不需要更改就需要使用Element-UI自带的表格组件来模拟Ag-Grid网格的样式和配置方式,但是时间有限,只能模拟个皮毛,但只做展示完全够用了。

具体实现

原Ag-Grid封装组件

CommonTable.vue

<template>
  <!--列表-->
  <div>
    <ag-grid-vue
      class="ag-theme-balham common-list"
      v-show="showCommonTable"
      v-loading="loading"
      :columnDefs="columnDefs"
      :rowData="dataList" :gridOptions="gridOptions"
      @grid-ready="handleGridReady"
      :style="listStyle"/>
  </div>
</template>

<script>
import {AgGridVue} from 'ag-grid-vue'

export default {
  name: 'CommonTable',
  components: {
    AgGridVue
  },
  data() {
    return {
      // Loading加载
      loading: true,
      // 列表样式
      listStyle: {
        height: 'calc(100vh - 277px)'
      },
      // 没有编辑之前的列表数据
      backupsList: [],
      // 列表数据
      dataList: [],
      // 列表标题
      columnDefs: [],
      // 是否展示列表
      showCommonTable: true,
      // 列表编辑控制
      editTableFlag: true,
      // 列表配置
      gridOptions: {
        onGridReady() {
          //自适应
          this.api.sizeColumnsToFit()
        },
        // 设置行样式
        getRowStyle(params) {
          let style = {'font-size': "18px"}
          if (params.node.rowIndex % 2 !== 0) {
            style.background = '#FFF1EB'
          }
          if (params.data && params.data.fixedBottom && params.data.fixedBottom === true) {

            style.background = "#fccfba"
          }
          return style
        },
        // 单击单元格事件
        onCellClicked(cell) {
        },
        // 行编辑完成事件
        onRowEditingStopped: (e) => {
        },
        // 双击单元格事件
        onCellEditingStarted: (e) => {
        },
        // 单元格值改变事件
        onCellValueChanged: (e) => {
        },
        // 单元格编辑完成事件
        onCellEditingStopped: (e) => {

        },
        defaultColDef: {
          // 单元表格是否可编辑
          editable: () => {
            return this.editTableFlag;
          },//单元表格是否可编辑
          // 是否可以调整列大小,就是拖动改变列大小
          resizable: true,
          //如果是数字,右对齐
          cellClass(params) {
            return params.colDef.type === 'numericColumn' ? 'ag-numeric-cell' : '';
          },
          sortable: true
        },
        // 可多选
        rowSelection: 'multiple',
        // 行高
        rowHeight: 30,
        // 表头的高度
        headerHeight: 46,
        // 单击编辑
        singleClickEdit: true,
        // 数据加载
        overlayLoadingTemplate: '数据加载中...',
        //无数据场景
        overlayNoRowsTemplate: '暂无数据',
        // 允许行合并
        suppressRowTransform: true,
        // 无实际意义(与ElCommonTable的方法保持一直)防止报错
        customSpanHandler: null
      },
      gridApi: {}
    }
  },
  methods: {
    /**
     * 设置列表样式
     */
    setListStyle(data) {
      this.listStyle = data
    },
    //刷新
    freshAggrid(){
      this.gridApi.sizeColumnsToFit()
    },
    /**
     * 设置列表属性
     */
    setGridOptions(data) {
      this.gridOptions = data
    },
    /**
     * 设置列表标题
     */
    setColumnDefs(data) {
      this.columnDefs = data
    },
    // /**
    //  * 重新设置列标题
    //  */
    // resetSetColumnDefs(obj) {
    //   this.gridApi.setColumnDefs(obj)
    //   if(!this.autoSize){
    //     //表格自适应
    //     this.gridApi.sizeColumnsToFit()
    //   }
    // },

    /**
     * 编辑
     * @param flag
     */
    toEdit(flag) {
      if (typeof flag === 'undefined') {
        this.editTableFlag = !this.editTableFlag
      } else {
        this.editTableFlag = flag;
      }
    },
    /**
     * 设置列表数据
     */
    setDataList(data) {
      this.loading = false
      if (data !== undefined && data !== null && data.length !== 0) {
        this.gridApi.sizeColumnsToFit()
      }
      this.dataList = JSON.parse(JSON.stringify(data))
      this.backupsList = JSON.parse(JSON.stringify(data))
    },
    /**
     * 新增数据
     */
    addData(data) {
      this.loading = false
      if (data === undefined || data === null) {
        data = {}
      }
      this.dataList.push(data)
    },
    /**
     * 新增数据在第一行
     */
    addDataFirst(data) {
      this.loading = false
      if (data === undefined || data === null) {
        data = {}
      }
      this.dataList.unshift(data)
    },
    /**
     * 重新设置标题
     */
    resetSetColumnDefs(data) {
      this.gridApi.setColumnDefs(data)
      //表格自适应
      this.gridApi.sizeColumnsToFit()
    },
    /**
     * 获取没有编辑之前的列表数据
     */
    getBackupsList() {
      return this.backupsList
    },
    /**
     * 获取编辑之后的列表数据
     */
    getDataList() {
      return this.dataList
    },
    /**
     * 是否展示列表
     */
    isShowCommonTable(boolean) {
      this.showCommonTable = boolean
    },
    /**
     * 获取列表id集合
     */
    getRowsIds() {
      let selRows = this.gridApi.getSelectedRows()
      if (selRows == null || selRows.length <= 0) {
        return null
      }
      let ids = []
      selRows.forEach(e => {
        ids.push(e.id)
      })
      return ids
    },
    /**
     * 列表创建完成后执行的事件
     */
    handleGridReady(params) {
      this.gridApi = params.api
      this.columnApi = params.columnApi
      this.gridApi.sizeColumnsToFit()
    },
    /**
     * 还原到没有编辑之前的列表
     */
    reductionList() {
      this.dataList = JSON.parse(JSON.stringify(this.backupsList))
    },
    /**
     * 结束单元格的编辑
     */
    stopUpdate() {
      this.gridApi.stopEditing()
    },
    /**
     * 打开遮罩层
     */
    openLoading() {
      this.loading = true
    },
    /**
     * 获取AgGridAPI
     */
    getAgGridAPI() {
      return this.gridApi;
    },
    setPinnedTopRowData(data) {
      this.gridApi.setPinnedTopRowData(data);
    },
    setPinnedBottomRowData(data) {
      this.gridApi.setPinnedBottomRowData(data);
    },
    /**
     * 合行列并处理
     */
    setCustomSpanHandler(fn) {
      if(Object.prototype.toString.call(fn) !== '[object Function]') {
        throw new Error("customSpanHandler必须传递函数!");
      }
      this.customSpanHandler = fn;
    }
  }

}
</script>

<style scoped>
/deep/ .show-cell {
  background: white;
  border-top: 0.1px solid lightgrey ;
  border-left: 0.1px solid lightgrey !important;
  border-right: 0.1px solid lightgrey !important;
  border-bottom: 0.1px solid lightgrey !important;
}
</style>

解决Element-UI和Ag-Grid的多配置差异

原始差异
  • Element-UI

    <template>
      <el-table
        :data="tableData"
        style="width: 100%">
        <el-table-column prop="date" label="日期" width="150"></el-table-column>
        <el-table-column label="配送信息">
          <el-table-column prop="name" label="姓名" width="120"></el-table-column>
          <el-table-column label="地址">
            <el-table-column prop="province" label="省份" width="120"></el-table-column>
            <el-table-column prop="city" label="市区" width="120"></el-table-column>
            <el-table-column prop="address" label="地址" width="300"></el-table-column>
            <el-table-column prop="zip" label="邮编" width="120"></el-table-column>
          </el-table-column>
        </el-table-column>
      </el-table>
    </template>
    
    
  • Ag-Grid

    [
            {
              field: "date",
              headerName: "日期",
              width:150
            },
            {
              headerName: "配送信息",
              children: [
                {
                  field: "name",
                  headerName: "姓名",
                  width: 120
                },
                {
                  headerName: "地址",
                  children: [
                    {
                      field: "province",
                      headerName: "省份",
                      width: 120
                    },
                    {
                      field: "city",
                      headerName: "市区",
                      width: 120
                    },
                    {
                      field: "address",
                      headerName: "地址",
                      width: 300
                    },
                    {
                      field: "zip",
                      headerName: "邮编",
                      width: 120
                    },
                  ]
                },
              ]
            },
          ]
    
解决差异

采用Ag-Grid的表头配置格式,将Element-UI的Table组件进行深度优先的组件封装

image (5).png

ElCommonTable组件(Index.vue)

注意:这里面代码中的方法和CommonTable组件保持一致,因为原始的功能模块可能调用的CommonTable组件的方法,如果静默替换掉,则有可能在ElCommonTable找不到方法,就会报错。所以就算ElCommonTable中的方法没有用,但是CommonTable组件中有此方法,ElCommonTable必须也得有。

<template>
  <div class="el-table-container" :style="listStyle">
    <el-table
      :data="dataList"
      size="mini"
      :fit="true"
      height="100%"
      min-height="100%"
      :span-method="mySpanHandler"
      @cell-mouse-enter="handleCellEnter"
      @cell-mouse-leave="handleCellLeave"
      @cell-click="handleCellClick"
    >
      <!--<el-table-column-->
      <!--  type="selection"-->
      <!--  width="55"-->
      <!--  align="center"-->
      <!--&gt;-->
      <!--</el-table-column>-->
      <MyElTableColumn :column-item="item" v-for="(item, index) in columnDefs"></MyElTableColumn>
    </el-table>
  </div>


</template>

<script>
import MyElTableColumn from "@/components/ElCommonTable/MyElTableColumn"
export default {
  name: 'ElCommonTable',
  props: {
    spanHandler: Function
  },
  components: {
    MyElTableColumn
  },
  computed: {
    mySpanHandler() {
      if(typeof this.spanHandler !== 'undefined') {
        return this.spanHandler
      } else if(this.customSpanHandler) {
        return this.customSpanHandler
      }
    }
  },
  data() {
    return {
      // Loading加载
      loading: true,
      // 列表样式
      listStyle: {
        height: 'calc(100vh - 277px)'
      },
      // 没有编辑之前的列表数据
      backupsList: [],
      // 列表数据
      dataList: [],
      // 列表标题
      columnDefs: [],
      // 是否展示列表
      showCommonTable: true,
      // 列表编辑控制
      editTableFlag: true,
      // 列表配置
      gridOptions: {

      },
      // 行列合并处理
      customSpanHandler: null
    }
  },
  methods: {
    /**
     * 设置列表样式
     */
    setListStyle(data) {
      this.listStyle = data
    },

    /**
     * 设置列表属性
     */
    setGridOptions(data) {
      this.gridOptions = data
    },
    /**
     * 设置列表标题
     */
    setColumnDefs(data) {
      this.columnDefs = data
    },


    /**
     * 编辑
     * @param flag
     */
    toEdit(flag) {
      if (typeof flag === 'undefined') {
        this.editTableFlag = !this.editTableFlag
      } else {
        this.editTableFlag = flag;
      }
    },
    /**
     * 设置列表数据
     */
    setDataList(data) {
      this.loading = false
      this.dataList = JSON.parse(JSON.stringify(data))
      this.backupsList = JSON.parse(JSON.stringify(data))
    },
    /**
     * 新增数据
     */
    addData(data) {
      this.loading = false
      if (data === undefined || data === null) {
        data = {}
      }
      this.dataList.push(data)
    },
    /**
     * 新增数据在第一行
     */
    addDataFirst(data) {
      this.loading = false
      if (data === undefined || data === null) {
        data = {}
      }
      this.dataList.unshift(data)
    },
    /**
     * 重新设置标题
     */
    resetSetColumnDefs(data) {
     
    },
    /**
     * 获取没有编辑之前的列表数据
     */
    getBackupsList() {
      return this.backupsList
    },
    /**
     * 获取编辑之后的列表数据
     */
    getDataList() {
      return this.dataList
    },
    /**
     * 是否展示列表
     */
    isShowCommonTable(boolean) {
      this.showCommonTable = boolean
    },
    /**
     * 获取列表id集合
     */
    getRowsIds() {
     return []
    },
    /**
     * 列表创建完成后执行的事件
     */
    handleGridReady(params) {
      
    },
    /**
     * 还原到没有编辑之前的列表
     */
    reductionList() {
      this.dataList = JSON.parse(JSON.stringify(this.backupsList))
    },
    /**
     * 结束单元格的编辑
     */
    stopUpdate() {
     
    },
    /**
     * 打开遮罩层
     */
    openLoading() {
      this.loading = true
    },
    /**
     * 获取AgGridAPI
     */
    getAgGridAPI() {
      
    },
    setPinnedTopRowData(data) {
      
    },
    setPinnedBottomRowData(data) {
      
    },


    /** 鼠标移入cell */
    handleCellEnter (row, column, cell, event) {

    },
    /** 鼠标移出cell */
    handleCellLeave (row, column, cell, event) {

    },
    /** 点击cell */
    handleCellClick (row, column, cell, event) {

    },
    /** 取消编辑状态 */
    cancelEditable (cell) {

    },

    /**
     * 合行列并处理
     */
    setCustomSpanHandler(fn) {
      if(Object.prototype.toString.call(fn) !== '[object Function]') {
        throw new Error("customSpanHandler必须传递函数!");
      }
      this.customSpanHandler = fn;
    }

  }

}
</script>

<style lang='scss'>
.item{
  .item__input{
    display: none;
    width: 100px;
    /* 调整elementUI中样式 如果不需要调整请忽略 */
    .el-input__inner{
      height: 24px!important;
    }
    /* 调整elementUI中样式 如果不需要调整请忽略 */
    .el-input__suffix{
      i{
        font-size: 12px !important;
        line-height: 26px !important;
      }
    }
  }
  .item__txt{
    box-sizing: border-box;
    border: 1px solid transparent;
    width: 100px;
    line-height: 24px;
    padding: 0 8px;
  }
  .item__txt--hover{
    border: 1px solid #dddddd;
    border-radius: 4px;
    cursor: text;
  }
}
</style>


ElCommonTable组件(MyElTableColumn.vue)

<template>

  <el-table-column   :prop="columnItem.field"
                     :label="columnItem.headerName"
                     :width="columnItem.width"
                     align="center">


    <template v-for="(sonItem, sonIndex) in columnItem.children">
      <MyElTableColumn v-if="sonItem.children" :key="`column_${sonIndex}`"  :column-item="sonItem"></MyElTableColumn>
      <el-table-column
        v-else
        :key="`sonItem_${sonIndex}`"
        :label="sonItem.headerName"
        :prop="sonItem.field"
        align="center"
      ></el-table-column>
    </template>
  </el-table-column>
</template>

<script>
export default {
  name: "MyElTableColumn",
  props: {
    columnItem: Object,
  }

}
</script>

<style lang="scss">
.el-table th.el-table__cell{
  user-select: inherit !important;
}
</style>

测试组件(Test.vue)

<template>
  <div class="test-container" style="width: 80%">
    <h1>TEST</h1>
    <ElCommonTable ref="commonTable"></ElCommonTable>
  </div>
</template>

<script>
import ElCommonTable from  "@/components/ElCommonTable/index"
import {listProduction} from "@/api/dispatch/xcmineone/production";
export default {
  name: "Test",
  components: {
    ElCommonTable
  },
  data() {
    return {
      // 查询参数
      queryParams: {
        time: this.getCurrentDate(),
        pageSize: 100
      },
    }
  },
  mounted() {
    this.initModular()
  },
  methods:{
    /**
     * 初始化组件
     */
    initModular() {
      this.$refs.commonTable.openLoading();
      this.$refs.commonTable.toEdit(false)
      this.$refs.commonTable.setColumnDefs(this.getTitleList());

      //ElCommonTable合并配置(编程模式)
      // this.$refs.commonTable.setCustomSpanHandler(({ row, column, rowIndex, columnIndex }) => {
      //   if (rowIndex % 2 === 0) {
      //     if (columnIndex === 0) {
      //       return [1, 2];
      //     } else if (columnIndex === 1) {
      //       return [0, 0];
      //     }
      //   }
      // })
      this.getList();
    },
    /**
     * 查询列表
     */
    getList() {
      setTimeout(() => {
        let tableData = [{
          date: '2016-05-03',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-02',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-04',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-01',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-08',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-06',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-07',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }]
        this.$refs.commonTable.setDataList(tableData);
      } ,300)
      // listProduction(this.queryParams).then(res => {
      //   this.$refs.commonTable.setDataList(res.rows);
      // })
    },
    /**
     * 获取初始列表
     */
    getTitleList() {
      return [
        {
          field: "date",
          headerName: "日期",
          width:150
        },
        {
          headerName: "配送信息",
          children: [
            {
              field: "name",
              headerName: "姓名",
              width: 120
            },
            {
              headerName: "地址",
              children: [
                {
                  field: "province",
                  headerName: "省份",
                  width: 120
                },
                {
                  field: "city",
                  headerName: "市区",
                  width: 120
                },
                {
                  field: "address",
                  headerName: "地址",
                  width: 300
                },
                {
                  field: "zip",
                  headerName: "邮编",
                  width: 120
                },
              ]
            },
          ]
        },
      ]
    },
    /**
     * ElCommonTable参数传递(参数传递模式,优先级高)
     * @param row
     * @param column
     * @param rowIndex
     * @param columnIndex
     * @returns {number[]}
     */
    // spanHandler ({ row, column, rowIndex, columnIndex }) {
    //   if (rowIndex % 4 === 0) {
    //     if (columnIndex === 0) {
    //       return [1, 2];
    //     } else if (columnIndex === 1) {
    //       return [0, 0];
    //     }
    //   }
    // },
  }
}
</script>

<style scoped>

</style>

效果示例

image (6).png

复制也没问题

image (7).png

注意:这种差异可能之后会有很多,比如行列合并等,目前只是实现了多级表头配置和数据渲染,但这对目前仅作为查看数据的表格足够了,只是一个思路,后续用到后续再扩展。

编辑切换

代码实现

<template>
  <div class="test-container" style="width: 80%">
    <h1>TEST</h1>
    <el-button @click="handleEdit" type="primary">编辑</el-button>
    <el-button v-if="editable" @click="handleCancel">取消</el-button>
    <!--切换表格组件-->
    <CommonTable v-if="editable" ref="commonTable"/>
    <ElCommonTable v-else ref="commonTable"></ElCommonTable>
  </div>
</template>

<script>
import ElCommonTable from  "@/components/ElCommonTable/index"
export default {
  name: "Test",
  components: {
    ElCommonTable
  },
  data() {
    return {
      // 编辑控制
      editable: false,
      // 查询参数
      queryParams: {
        time: this.getCurrentDate(),
        pageSize: 100
      },
    }
  },
  watch: {
    editable(newVal) {
	  //这里必须在页面加载完成后再加载
      this.$nextTick(vm => {
        this.initModular()
      })
    }
  },
  mounted() {
    this.initModular()
  },
  methods:{
    /**
     * 初始化组件
     */
    initModular() {
      this.$refs.commonTable.openLoading();
      this.$refs.commonTable.setColumnDefs(this.getTitleList());

      //ElCommonTable合并配置(编程模式)
      // this.$refs.commonTable.setCustomSpanHandler(({ row, column, rowIndex, columnIndex }) => {
      //   if (rowIndex % 2 === 0) {
      //     if (columnIndex === 0) {
      //       return [1, 2];
      //     } else if (columnIndex === 1) {
      //       return [0, 0];
      //     }
      //   }
      // })
      this.getList();
    },

    /**
     * 修改按钮操作
     */
    handleEdit() {
      this.editable = true
      this.$refs.commonTable.toEdit(true)

    },
    /**
     * 取消按钮操作
     */
    handleCancel() {
      this.editable = false;
      this.$refs.commonTable.toEdit(false)
      this.getList();

    },
    /**
     * 查询列表
     */
    getList() {
      setTimeout(() => {
        let tableData = [{
          date: '2016-05-03',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-02',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-04',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-01',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-08',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-06',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }, {
          date: '2016-05-07',
          name: '王小虎',
          province: '上海',
          city: '普陀区',
          address: '上海市普陀区金沙江路 1518 弄',
          zip: 200333
        }]
        this.$refs.commonTable.setDataList(tableData);
      } ,300)
      // listProduction(this.queryParams).then(res => {
      //   this.$refs.commonTable.setDataList(res.rows);
      // })
    },
    /**
     * 获取初始列表
     */
    getTitleList() {
      return [
        {
          field: "date",
          headerName: "日期",
          width:150
        },
        {
          headerName: "配送信息",
          children: [
            {
              field: "name",
              headerName: "姓名",
              width: 120
            },
            {
              headerName: "地址",
              children: [
                {
                  field: "province",
                  headerName: "省份",
                  width: 120
                },
                {
                  field: "city",
                  headerName: "市区",
                  width: 120
                },
                {
                  field: "address",
                  headerName: "地址",
                  width: 300
                },
                {
                  field: "zip",
                  headerName: "邮编",
                  width: 120
                },
              ]
            },
          ]
        },
      ]
    },
    /**
     * ElCommonTable参数传递(参数传递模式,优先级高)
     * @param row
     * @param column
     * @param rowIndex
     * @param columnIndex
     * @returns {number[]}
     */
    // spanHandler ({ row, column, rowIndex, columnIndex }) {
    //   if (rowIndex % 4 === 0) {
    //     if (columnIndex === 0) {
    //       return [1, 2];
    //     } else if (columnIndex === 1) {
    //       return [0, 0];
    //     }
    //   }
    // },
  }
}
</script>

<style scoped>

</style>

最终效果

在这里插入图片描述

在这里插入图片描述

表格样式不一样伙计们先不要在乎,可调,这样虽然在切换的时候有一瞬间局部白屏,目前没办法,除非表格组件不动才可以使用Vue的响应式效果。

结语

又是码代码的一天~

唉,就算这样实现,避免了一部分大量替换表格组件的工作量,但还是需要针对某个表格进行调整,并没有完全解放,不过这是经团队商讨,目前最可行的方案了。

但是一个作为完美主义且用情专一的我来说,这不是我的风格~但是我最后屈尊于现实,在每个月黑风高孤独的夜晚中,我都忍不住陷入了深深的思绪中:

如果一开始我们不选择这个Ag-Grid网格,而是选择其他的。

如果一开始我们确定使用她并对他进行了各种的深度调研。

如果一开始我们考虑的再周全点。

如果一开始没有发生这一切。

如果给我个点赞鸡腿~

做梦中… …

求关注.jpg

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值