AG Grid 手册

# 版本升级

每次升级前先查阅 AG 官方的 Changelog,看有没有重大更新,以及废弃的 API。
访问这种格式的 URL https://ag-grid.com/changelog/?fixVersion=28.0.0 可以查看某个版本详细的更新说明(也可以在右上角下拉切换版本号)。

# 升级 v28

查阅 Release Notes 可以看到有一条重要的更新:

AG-6948 - @ag-grid-community/all-modules and @ag-grid-enterprise/all-modules have been removed. If using @ag-grid-community/all-modules you should switch to use Packages instead. If you want to use individual modules for a small package size, use Modules.

大意是从 v28 开始,不再支持 /all-modules 的用法,而且 @ag-grid-community/all-modulesnpm 官方页面也显示“This package has been deprecated”。

因此,从 v28 开始,必须采用 AG 所谓的 Packages 写法。

可能涉及的改动如下:

  • 修改 package.json 中的包名:

      "dependencies": {
        "ag-grid-community": "~28.1.1",
        "ag-grid-enterprise": "~28.1.1",
        "ag-grid-vue": "~28.1.1",
      },
    复制代码
  • 修改 import 语句中的包名,同时,原先的 modules 写法需要弃用

    • @ag-grid-enterprise => ag-grid-enterprise
    • @ag-grid-community => ag-grid-community
    • @ag-grid-community/vue => ag-grid-vue
    // 修改 vue 包名
    import { AgGridVue } from '@ag-grid-community/vue'
    // 改成 =>
    import { AgGridVue } from 'ag-grid-vue'
    
    // 移除后面的 modules,直接从包名导入
    import { AllModules } from '@ag-grid-enterprise/all-modules'
    // 改成 =>
    import { AllModules } from 'ag-grid-enterprise'
    
    import { LicenseManager } from '@ag-grid-enterprise/core'
    // 同理,改成 =>
    import { LicenseManager } from 'ag-grid-enterprise'
    
    import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'
    // 同理,改成 =>
    import { ClientSideRowModelModule } from 'ag-grid-community'
    复制代码

# 主题定制

自 v28 版本开始,AG 在主题中引入了 CSS 变量,实现快速修改默认的样式。

详细内容可以参考文档:

# 主题文件引入

主题的引入有多种方式,按推荐顺序进行介绍。

# 使用 @cndinfo/cube-design-web 组件库

如果有使用我们的 @cndinfo/cube-design-web 组件库,它已经内置了 ag 的 balham 主题,不需要重复引入。直接引入组件库即可:

import cubeDesignWeb from '@cndinfo/cube-design-web'
Vue.use(cubeDesignWeb)
复制代码

# CDN 方式引入

在项目的 index.html 中使用 CDN 链接引入:

<link rel="stylesheet" href="https://front-end-huawei-cdn.devops.cndinfo.com/npm/ag-grid-community@28.1.1/styles/ag-grid.min.css" />
<link rel="stylesheet" href="https://front-end-huawei-cdn.devops.cndinfo.com/npm/ag-grid-community@28.1.1/styles/ag-theme-balham.min.css" />
复制代码

这种方式可以保证 CSS 文件加载顺序早于 JS。

# main.js 中导入

main.js 开头导入 npm 包中的样式文件:

// main.js
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-balham.css'
复制代码

这种方式可以也保证 CSS 的加载顺序早于 grid 组件。

# 在 Sass 文件中导入

在 Sass 中引用一共有三种方式:

  1. 引用 CDN 文件
@import 'https://front-end-huawei-cdn.devops.cndinfo.com/npm/ag-grid-community@28.1.1/styles/ag-grid.min.css';
@import 'https://front-end-huawei-cdn.devops.cndinfo.com/npm/ag-grid-community@28.1.1/styles/ag-theme-balham.min.css';
复制代码
  1. 引用本地 CSS 文件
@import '~ag-grid-community/styles/ag-grid.css';
@import '~ag-grid-community/styles/ag-theme-balham.css';
复制代码
  1. 引用本地 Sass 文件 如果倾向使用 Sass 的特性,也可以使用 Sass 的方式:Sass Styling API
/* ag-grid.scss */
@use "~ag-grid-community/styles" as ag;
@include ag.grid-styles((
  theme: alpine
));
复制代码

需要注意:包含上述代码的 .scss 文件必须在 main.js 开头导入。

如果不在开头导入,会导致 ag CSS 中和 grid 尺寸相关(例如 --ag-row-height, --ag-header-height)的变量无法生效。
原因是 ag 在渲染 grid 的过程中,针对尺寸相关会生成行内样式(inline style),行内样式优先级是最高的,无法被其他方式覆盖。

官方文档对此也有提醒:

Firstly, the grid will attempt to measure the size of an element. This works when styles have loaded, but will not work if the grid initialises before the theme loads.

# 修改样式

在对应的主题中对 CSS 变量进行重新赋值。

.ag-theme-balham {
  --ag-data-color: red;
  --ag-checkbox-border-radius: 4px;
  --ag-header-height: 100px;
  --ag-row-height: 40px;
  --ag-list-item-height: 40px;
  --ag-font-size: 22px;
}
复制代码

完整的变量清单参考: CSS Variable Reference

如果自定义的没有生效,可以按下面的步骤来排查:

  1. 多添加几个变量,看是否有任一变量生效,如果都没生效,可能是样式 CSS 主题文件没有引入成功,重新根据文档检查
  2. 部分变量生效,部分变量未生效:检查未生效变量定义的样式是否被通过 class 定义的样式覆盖了
  3. 尺寸相关的变量未生效:按上述文档排查主题样式文件的引用方式,是否早于 ag-grid 初始化

# 保存用户表头操作

本例文档根据引用 page-mixins.js 文件改造

# page-mixins.js 文件改造

export const GridPageMixin = {
  data() {
    return {
      ...
      columnDefsItems: {} // 接口返回用户操作对象
    }
  }methods: {
    /**
     * ag-grid api返回的平铺数据转换成同父级map(),考虑多行表头
     * @param {*} a 数组数据
     * @param {*} key 判断相同父级字段
     */
    arrMap(a, key) {
      var map = a.reduce((all, m) => {
        let list = all.get(m[key])
        if (!list) {
          list = []
          all.set(m[key], list)
        }
        list.push(m)
        return all
      }, new Map())
      return map
    }
  },
  /**
    * 将原始表头数据columnDefs匹配接口返回的用户操作数据columnDefsItems(index: 顺序, hide: 隐藏, width: 宽度 , sort: 排序)
    * @param {*} arr 原始表头数据columnDefs
    * @param {*} ref 当前表头数组所属表头的ref值
    * @param {*} parent arr为子级时,所属的父级元素
    * @param {*} child 是否为子级
    */
  setColumnDefs(arr, ref, parent, child = false) {
    arr.filter((item, index) => {
      item.index = child ? this.columnDefsItems[ref]?.[parent.field]?.children?.[item.field]?.index : this.columnDefsItems[ref]?.[item.field]?.index
      item.hide = child ? this.columnDefsItems[ref]?.[parent.field]?.children?.[item.field]?.hide : this.columnDefsItems[ref]?.[item.field]?.hide || false
      item.width = child ? this.columnDefsItems[ref]?.[parent.field]?.children?.[item.field]?.width : this.columnDefsItems[ref]?.[item.field]?.width || item.width
      item.sort = child ? this.columnDefsItems[ref]?.[parent.field]?.children?.[item.field]?.sort : this.columnDefsItems[ref]?.[item.field]?.sort || item.sort
      if (item.children) {
        item.children = this.setColumnDefs(item.children, ref, item, true)
      }
    })
    arr.sort((a, b) => a.index < b.index ? -1 : a.index > b.index ? 1 : 0)
    return arr
  },
  /**
    * 页面刷新获得该路由下用户保存的操作数据,并使用setColumnDefs api将重置后的数据渲染到表头
    * @param {*} columnDefs 原始表头数据
    * @param {*} ref 当前表头数组所属表头的ref值
    */
  getParaminfoPageCode(columnDefs, ref) {
    [api接口](this.$route.name).then(res => {
      if (res?.data?.sParamValue) {
        const data = JSON.parse(res.data.sParamValue)
        this.columnDefsItems = data
        columnDefs = this.setColumnDefs(columnDefs, ref)
      }
      this.$refs[ref]?.gridOptions.api.setColumnDefs(columnDefs)
    })
  },
  /**
    * 操作保存接口
    * @param {*} data 保存数据, sPageCode:页面路由this.$route.name, sParamValue:用户操作数据
    */
  getParaminfoSave(data) {
    paraminfoSave(data)
  },
  /**
    * 针对多个列表未使用initDefaultGrid()方法的列表,修改使用setDefaultColumnDefs()
    * @param {*} ref 该列表的ref值
    */
  setDefaultColumnDefs(ref) {
    const gridOptions = {
      // ag=grid 25版本由applyColumnDefOrder控制实现按顺序渲染,26版本后支持列表自动按顺序渲染
      applyColumnDefOrder: true,
      // 列表隐藏/显示触发的api
      onColumnVisible: params => {
        params.source === 'toolPanelUi' && this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      // 列表拖拽触发的api
      onDragStopped: params => {
        this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      // 列表排序触发的api
      onSortChanged: params => {
        this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      // 开启列面板
      sideBar: {
        toolPanels: [{
          id: 'columns',
          domLayout: 'autoHeight',
          labelDefault: 'Columns',
          labelKey: 'columns',
          iconKey: 'columns',
          toolPanel: 'agColumnsToolPanel',
          toolPanelParams: {
            suppressRowGroups: true,
            suppressValues: true,
            suppressPivots: true,
            suppressPivotMode: true,
            suppressColumnFilter: true,
            suppressColumnSelectAll: true,
            suppressColumnExpandAll: true
          }
        }]
      }
    }
    // 渲染原始表头数据columnDefs后,调用接口获得用户操作数据进行第二次渲染
    this.$nextTick(() => {
      ref && this.getParaminfoPageCode(this[ref].columnDefs, ref, false)
    })
    return gridOptions
  },
  initDefaultGrid(ops) {
    ...,
    const ref = options.ref
    // 如果存在ref,则调用接口进行匹配用户操作数据进行渲染
    ref && this.getParaminfoPageCode(columnDefs, ref)
    const gridOptions = {
      // ag=grid 25版本由applyColumnDefOrder控制实现按顺序渲染,26版本后支持列表自动按顺序渲染
      applyColumnDefOrder: true,
      // 列表隐藏/显示触发的api
      onColumnVisible: params => {
        params.source === 'toolPanelUi' && this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      // 列表拖拽触发的api
      onDragStopped: params => {
        this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      // 列表排序触发的api
      onSortChanged: params => {
        this.setColumnDefsItems(params, this.columnDefsItems, ref)
      },
      sideBar: {
        toolPanels: [{
          id: 'columns',
          domLayout: 'autoHeight',
          labelDefault: 'Columns',
          labelKey: 'columns',
          iconKey: 'columns',
          toolPanel: 'agColumnsToolPanel',
          toolPanelParams: {
            suppressRowGroups: true,
            suppressValues: true,
            suppressPivots: true,
            suppressPivotMode: true,
            suppressColumnFilter: true,
            suppressColumnSelectAll: true,
            suppressColumnExpandAll: true
          }
        }]
      },
    }
    // 如果不存在ref,则使用原始表头数据进行渲染
    !ref && (gridOptions.columnDefs = columnDefs)
    ...
  }
}
复制代码

# 项目页面改造

# ag-grid-vue 新增 ref 属性

<ag-grid-vue
  ref="ksGridOptions"
  :suppress-copy-rows-to-clipboard="true"
  class="ag-theme-balham grid-class border-none"
  :row-drag-managed="true"
  :grid-options="ksGridOptions"
  :row-data="ksGridData"
  :modules="aGGridAllModules"
/>
复制代码

# grid-options 对象新增 ref 属性

多列表重置后的表头数据可根据ref渲染到指定列表

  1. 页面表格使用 initDefaultGrid() 方法时
<ag-grid-vue
  ref="ksGridOptions"
  :suppress-copy-rows-to-clipboard="true"
  class="ag-theme-balham grid-class border-none"
  :row-drag-managed="true"
  :grid-options="ksGridOptions"
  :row-data="ksGridData"
  :modules="aGGridAllModules"
/>

export default {
  data() {
    return {
      khGridOptions: {
        ref: 'ksGridOptions', // 传入对应的列表ref值
        ...
      }
    }
  }
}

复制代码
  1. 页面表格未使用 initDefaultGrid()
<ag-grid-vue
  ref="ksGridOptions"
  :suppress-copy-rows-to-clipboard="true"
  class="ag-theme-balham grid-class border-none"
  :row-drag-managed="true"
  :grid-options="ksGridOptions"
  :row-data="ksGridData"
  :modules="aGGridAllModules"
/>

export default {
  data() {
    return {
      ksGridOptions: {
        ...
        ...this.setDefaultColumnDefs('ksGridOptions') // 引入混入方法setDefaultColumnDefs(),并传入列表ref值
      }
    }
  }
}
复制代码

# suppressColumnsToolPanel 属性配置指定表头不出现在列面板上

const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'checkout',
      headerName: '',
      minWidth: 40,
      suppressColumnsToolPanel: true,
      maxWidth: 40,
      checkboxSelection: true,
      headerCheckboxSelection: true,
      'pinned': 'left'
    }
  ]
}
复制代码

# field 属性不可为空

一级表头,多级表头都不可为空,同一级表头 field 不可重复

// false
// 表头父级 field 为空
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: '',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: 'sAllotDate'
        }
      ]
    }
  ]
}

// false
// 表头子级 field 为空
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: ''
        }
      ]
    }
  ]
}

// false
// 同级 field 重复
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: ''
        }
      ]
    }{
      field: 'sAllotDate',
      headerName: '送达日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: ''
        }
      ]
    }
  ]
}

// false
// 同级 field 重复
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '仓库',
          field: 'vWarehouseInName'
        }{
          headerName: '公司',
          field: 'vWarehouseInName'
        }
      ]
    }
  ]
}

// true
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: 'sAllotDate'
        }
      ]
    }
  ]
}

// true
const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '公司',
          field: 'vCompanyInName'
        }{
          headerName: '仓库',
          field: 'vWarehouseInName'
        }
      ]
    }
  ]
}
复制代码

# 多级表头,父级新增 marryChildren 属性控制子级表头禁止移除父级

const gridOptions = {
  ref: 'gridOptions',
  columnDefs: [
    {
      field: 'sAllotDate',
      headerName: '调拨日期',
      marryChildren: true,
      children: [
        {
          headerName: '',
          field: 'sAllotDate'
        }
      ]
    },
    {
      headerName: '移出',
      field: 'vWarehouseName',
      marryChildren: true,
      children: [
        {
          field: 'vWarehouseName',
          headerName: '仓库'
        },
        {
          field: 'sExpectStockDate',
          headerName: '预计出仓日期'
        }
      ]
    }
  ]
}
复制代码