DevExtreme Vue PivotGrid 完整使用指南
适用场景:Vue 3 + DevExtreme PivotGrid,用于 ERP / 零售 / 销售 / 库存 / 毛利 / 指标分析等多维数据透视分析。
1. PivotGrid 是什么
PivotGrid 是 DevExtreme 提供的数据透视表组件,用来对多维数据进行分组、汇总、筛选、排序、展开、导出和状态保存。
典型业务场景:
- 按客户、门店、品牌、季节统计销售金额
- 按月份、年份统计销售趋势
- 按客户 + 月份分析销售金额、销售指标、毛利金额、毛利指标
- 按商品分类、门店、渠道分析库存和销售
- 类似 Excel 数据透视表的前端分析能力
2. 安装依赖
bash
npm install devextreme devextreme-vue
如果需要 Excel 导出,通常还需要:
bash
npm install exceljs file-saver
在项目入口或页面中引入样式:
js
import 'devextreme/dist/css/dx.light.css';
3. 基础引入
vue
<script setup>
import {
DxPivotGrid,
DxFieldPanel,
DxFieldChooser,
DxStateStoring,
DxExport,
DxTexts
} from 'devextreme-vue/pivot-grid';
</script>
4. 最小可用示例
vue
<template>
<DxPivotGrid
:data-source="dataSource"
:show-borders="true"
:allow-sorting="true"
:allow-filtering="true"
:height="600"
/>
</template>
<script setup>
import { ref } from 'vue';
import { DxPivotGrid } from 'devextreme-vue/pivot-grid';
const dataSource = ref({
fields: [
{
caption: '客户名称',
dataField: 'khmc',
area: 'row'
},
{
caption: '月份',
dataField: 'monthLabel',
area: 'column'
},
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
}
],
store: [
{
khmc: '直营1店',
monthLabel: '1月',
saleAmount: 1000
},
{
khmc: '直营1店',
monthLabel: '2月',
saleAmount: 2000
}
]
});
</script>
5. PivotGrid 核心结构
dataSource 通常由两部分组成:
js
const dataSource = ref({
fields: [],
store: []
});
| 配置 | 说明 |
|---|---|
fields |
字段定义,决定哪些字段放在行、列、数据、筛选区域 |
store |
实际数据源,可以是数组,也可以是远程数据源 |
dataFieldArea |
多个数据指标时,控制数据字段标题显示在行区还是列区 |
6. 四个核心区域 area
fields[].area 用来指定字段放在哪个区域。
| area | 含义 | 典型字段 |
|---|---|---|
row |
行维度 | 客户、门店、商品、品牌、季节 |
column |
列维度 | 年份、月份、日期、渠道 |
data |
数据指标 | 销售金额、数量、毛利、指标 |
filter |
筛选字段 | 区域、渠道、业务员、门店类型 |
示例:
js
fields: [
{
caption: '客户名称',
dataField: 'khmc',
area: 'row'
},
{
caption: '月份',
dataField: 'monthLabel',
area: 'column'
},
{
caption: '销售金额',
dataField: 'saleAmount',
area: 'data',
dataType: 'number',
summaryType: 'sum'
},
{
caption: '渠道',
dataField: 'channelName',
area: 'filter'
}
]
7. fields 字段完整说明
7.1 基础属性
| 属性 | 类型 | 说明 |
|---|---|---|
caption |
string | 字段显示名称 |
dataField |
string | 绑定数据源中的字段名 |
dataType |
string | 数据类型:string、number、date |
area |
string | 所属区域:row、column、data、filter |
width |
number | 字段宽度 |
visible |
boolean | 是否可见 |
visibleIndex |
number | 显示顺序 |
expanded |
boolean | 是否默认展开 |
示例:
js
{
caption: '客户名称',
width: 120,
dataField: 'khmc',
dataType: 'string',
area: 'row',
expanded: true
}
7.2 数据类型 dataType
| dataType | 说明 |
|---|---|
string |
字符串维度 |
number |
数字指标 |
date |
日期字段 |
金额、数量、指标字段必须建议配置:
js
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum'
}
否则容易被当成 count 计数,显示为 1。
7.3 汇总属性
主要用于 area: 'data' 的字段。
| 属性 | 说明 |
|---|---|
summaryType |
汇总方式 |
format |
格式化 |
summaryDisplayMode |
汇总显示模式,例如占比、差异 |
calculateCustomSummary |
自定义汇总逻辑 |
calculateSummaryValue |
对汇总结果二次计算 |
summaryType 常用值:
| 值 | 说明 |
|---|---|
sum |
求和 |
avg |
平均值 |
min |
最小值 |
max |
最大值 |
count |
计数 |
custom |
自定义汇总 |
示例:
js
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
}
7.4 format 格式化
常用格式:
js
format: {
type: 'fixedPoint',
precision: 2
}
常见类型:
| type | 说明 |
|---|---|
fixedPoint |
固定小数位 |
percent |
百分比 |
currency |
货币 |
decimal |
数字 |
示例:
js
{
caption: '毛利率',
dataField: 'grossProfitRate',
dataType: 'number',
area: 'data',
summaryType: 'avg',
format: {
type: 'percent',
precision: 2
}
}
7.5 排序属性
| 属性 | 说明 |
|---|---|
sortOrder |
排序方向:asc、desc |
allowSorting |
是否允许排序 |
allowSortingBySummary |
是否允许按汇总值排序 |
sortBySummaryField |
按指定汇总字段排序 |
sortBySummaryPath |
按指定路径下的汇总值排序 |
sortingMethod |
自定义排序方法 |
月份排序示例:
js
{
caption: '月份',
dataField: 'monthNum',
area: 'column',
sortOrder: 'asc',
customizeText(cellInfo) {
return cellInfo.value + '月';
}
}
建议:不要直接用
1月、2月、10月做排序字段,否则字符串排序容易出现1月、10月、11月、2月的问题。最好后端返回monthNum和monthLabel。
推荐数据结构:
js
{
monthNum: 1,
monthLabel: '1月'
}
7.6 筛选属性
| 属性 | 说明 |
|---|---|
filterValues |
默认筛选值 |
filterType |
筛选类型:include、exclude |
allowFiltering |
是否允许筛选 |
示例:
js
{
caption: '渠道',
dataField: 'channelName',
area: 'filter',
filterValues: ['直营'],
filterType: 'include'
}
7.7 分组属性
| 属性 | 说明 |
|---|---|
groupName |
分组名称 |
groupIndex |
分组顺序 |
groupInterval |
分组间隔 |
日期按年、月分组:
js
{
caption: '年份',
dataField: 'saleDate',
dataType: 'date',
area: 'column',
groupName: 'saleDateGroup',
groupInterval: 'year',
groupIndex: 0
},
{
caption: '月份',
dataField: 'saleDate',
dataType: 'date',
area: 'column',
groupName: 'saleDateGroup',
groupInterval: 'month',
groupIndex: 1
}
常见 groupInterval:
| 类型 | 可用值 |
|---|---|
| date | year、quarter、month、day、dayOfWeek |
| number | 数字间隔,例如 100、1000 |
7.8 自定义显示 customizeText
js
{
caption: '月份',
dataField: 'monthNum',
area: 'column',
customizeText(cellInfo) {
return cellInfo.value + '月';
}
}
金额加单位:
js
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
customizeText(cellInfo) {
return cellInfo.valueText + ' 元';
}
}
8. 多个 data 指标的正确配置
当有多个数据指标时,例如:
- 销售金额
- 销售指标
- 毛利金额
- 毛利指标
建议配置:
js
dataFieldArea: 'column'
完整示例:
js
const dataSource = ref({
fields: [
{
caption: '客户名称',
width: 120,
dataField: 'khmc',
area: 'row',
expanded: true
},
{
caption: '客户代码',
width: 120,
dataField: 'khdm',
area: 'row',
expanded: true
},
{
caption: '月份',
width: 120,
dataField: 'monthLabel',
area: 'column',
expanded: true,
sortOrder: 'asc'
},
{
caption: '销售金额',
width: 120,
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '销售指标',
width: 120,
dataField: 'zbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '毛利金额',
width: 120,
dataField: 'mlje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '毛利指标',
width: 120,
dataField: 'mlzbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
}
],
dataFieldArea: 'column',
store: []
});
显示结构:
text
1月
销售金额
销售指标
毛利金额
毛利指标
2月
销售金额
销售指标
毛利金额
毛利指标
9. Vue 页面完整示例
vue
<template>
<DxPivotGrid
ref="pivotGridRef"
id="pivotgrid"
:data-source="dataSource"
:height="pivotGridHeight"
:allow-sorting="true"
:allow-sorting-by-summary="true"
:allow-filtering="true"
:show-borders="true"
:show-column-grand-totals="true"
:show-row-grand-totals="true"
:show-row-totals="true"
:show-column-totals="true"
row-header-layout="standard"
@exporting="handleExport"
>
<DxExport :enabled="true" />
<DxStateStoring
:enabled="true"
type="localStorage"
storage-key="retail-sale-pivotgrid-analysis-state-v1"
:saving-timeout="300"
/>
<DxFieldPanel
:visible="true"
:allow-field-dragging="true"
:texts="{
columnFieldArea: '将列字段拖到此处',
dataFieldArea: '将数据字段拖到此处',
filterFieldArea: '将筛选字段拖到此处',
rowFieldArea: '将行字段拖到此处'
}"
/>
<DxTexts
grand-total="总计"
total="小计"
no-data="暂无数据"
show-field-chooser="显示字段选择器"
/>
<DxFieldChooser
:enabled="false"
title="字段选择器"
>
<DxTexts
all-fields="所有字段"
column-fields="列字段"
data-fields="数据字段"
filter-fields="筛选字段"
row-fields="行字段"
/>
</DxFieldChooser>
</DxPivotGrid>
</template>
<script setup>
import { ref } from 'vue';
import {
DxPivotGrid,
DxFieldPanel,
DxFieldChooser,
DxStateStoring,
DxExport,
DxTexts
} from 'devextreme-vue/pivot-grid';
const pivotGridRef = ref(null);
const pivotGridHeight = ref(700);
const dataSource = ref({
fields: [
{
caption: '客户名称',
width: 120,
dataField: 'khmc',
area: 'row',
expanded: true
},
{
caption: '客户代码',
width: 120,
dataField: 'khdm',
area: 'row',
expanded: true
},
{
caption: '月份',
width: 120,
dataField: 'monthLabel',
area: 'column',
expanded: true,
sortOrder: 'asc'
},
{
caption: '销售金额',
width: 120,
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '销售指标',
width: 120,
dataField: 'zbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '毛利金额',
width: 120,
dataField: 'mlje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
},
{
caption: '毛利指标',
width: 120,
dataField: 'mlzbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: {
type: 'fixedPoint',
precision: 2
}
}
],
dataFieldArea: 'column',
store: []
});
function loadData(res) {
const rows = res.data.map(item => ({
...item,
saleAmount: Number(item.saleAmount || 0),
zbje: Number(item.zbje || 0),
mlje: Number(item.mlje || 0),
mlzbje: Number(item.mlzbje || 0)
}));
dataSource.value = {
...dataSource.value,
store: rows
};
}
function handleExport(e) {
// Excel 导出逻辑见后文
}
</script>
10. 数据加载建议
10.1 推荐后端返回格式
json
{
"code": 200,
"msg": "success",
"data": [
{
"saleAmount": 1692710.0,
"khmc": "联营客户1",
"khdm": "LY001",
"mlje": 0.0,
"mlzbje": 0.0,
"zbje": 0.0,
"monthLabel": "1月"
}
]
}
10.2 前端统一转数字
js
const rows = result.data.map(item => ({
...item,
saleAmount: Number(item.saleAmount || 0),
zbje: Number(item.zbje || 0),
mlje: Number(item.mlje || 0),
mlzbje: Number(item.mlzbje || 0)
}));
dataSource.value = {
...dataSource.value,
store: rows
};
不建议只写:
js
dataSource.value.store = result.data;
在 Vue 响应式和 DevExtreme 组件刷新联动上,整体替换更稳。
11. StateStoring 状态保存
DxStateStoring 用来保存用户调整后的透视表状态,例如:
- 字段拖拽位置
- 排序
- 筛选
- 展开状态
- 字段布局
示例:
vue
<DxStateStoring
:enabled="true"
type="localStorage"
storage-key="retail-sale-pivotgrid-analysis-state-v1"
:saving-timeout="300"
/>
11.1 开发阶段建议关闭
字段配置还在调整时,建议:
vue
<DxStateStoring :enabled="false" />
否则旧缓存会覆盖新的 fields 配置,导致新字段不显示。
11.2 清理缓存
如果 storage-key 是:
text
retail-sale-pivotgrid-analysis-state
浏览器控制台执行:
js
localStorage.removeItem('retail-sale-pivotgrid-analysis-state')
11.3 正式项目建议给 key 加版本号
text
retail-sale-pivotgrid-analysis-state-v1
retail-sale-pivotgrid-analysis-state-v2
retail-sale-pivotgrid-analysis-state-v3
只要字段结构发生变化,就升级版本号,避免旧布局污染。
12. FieldPanel 字段面板
字段面板用于让用户拖拽字段。
vue
<DxFieldPanel
:visible="true"
:allow-field-dragging="true"
:texts="{
columnFieldArea: '将列字段拖到此处',
dataFieldArea: '将数据字段拖到此处',
filterFieldArea: '将筛选字段拖到此处',
rowFieldArea: '将行字段拖到此处'
}"
/>
| 属性 | 说明 |
|---|---|
visible |
是否显示字段面板 |
allowFieldDragging |
是否允许拖拽字段 |
texts |
区域提示文字 |
13. FieldChooser 字段选择器
字段选择器用于让用户选择哪些字段参与分析。
vue
<DxFieldChooser
:enabled="true"
title="字段选择器"
>
<DxTexts
all-fields="所有字段"
column-fields="列字段"
data-fields="数据字段"
filter-fields="筛选字段"
row-fields="行字段"
/>
</DxFieldChooser>
如果不希望用户自由调整字段,可以关闭:
vue
<DxFieldChooser :enabled="false" />
14. 合计与小计
常用配置:
vue
<DxPivotGrid
:show-column-grand-totals="true"
:show-row-grand-totals="true"
:show-row-totals="true"
:show-column-totals="true"
/>
| 属性 | 说明 |
|---|---|
showColumnGrandTotals |
显示列总计 |
showRowGrandTotals |
显示行总计 |
showRowTotals |
显示行小计 |
showColumnTotals |
显示列小计 |
中文文本:
vue
<DxTexts
grand-total="总计"
total="小计"
no-data="暂无数据"
/>
15. Excel 导出
安装:
bash
npm install exceljs file-saver
示例:
js
import { Workbook } from 'exceljs';
import { saveAs } from 'file-saver';
import { exportPivotGrid } from 'devextreme/excel_exporter';
function handleExport(e) {
const workbook = new Workbook();
const worksheet = workbook.addWorksheet('销售透视表');
exportPivotGrid({
component: e.component,
worksheet
}).then(() => {
workbook.xlsx.writeBuffer().then(buffer => {
saveAs(
new Blob([buffer], { type: 'application/octet-stream' }),
'销售透视表.xlsx'
);
});
});
e.cancel = true;
}
组件配置:
vue
<DxPivotGrid @exporting="handleExport">
<DxExport :enabled="true" />
</DxPivotGrid>
16. 常见问题
16.1 为什么数据区显示 1?
原因:字段被当成 count 计数了。
错误配置:
js
{
caption: '销售金额',
dataField: 'saleAmount',
area: 'data'
}
正确配置:
js
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum'
}
16.2 为什么新加字段不显示?
常见原因:
DxStateStoring旧缓存覆盖- 接口没有返回对应字段
- 字段名大小写不一致
- 字段值不是数字
- 多个 data 指标没有配置
dataFieldArea
处理方式:
js
localStorage.removeItem('你的 storage-key')
或者临时关闭:
vue
<DxStateStoring :enabled="false" />
16.3 为什么毛利金额不显示?
先看数据是否全是 0。
例如:
json
{
"mlje": 0.0
}
如果所有行都是 0,PivotGrid 即使显示也只有 0。可以临时造数据验证:
js
const rows = result.data.map(item => ({
...item,
mlje: 100,
zbje: 200,
mlzbje: 300
}));
如果这样能显示,说明组件没问题,是业务数据本身没有有效值。
16.4 为什么月份排序不对?
monthLabel: '10月' 会按字符串排序。
建议后端返回:
json
{
"monthNum": 10,
"monthLabel": "10月"
}
字段配置:
js
{
caption: '月份',
dataField: 'monthNum',
area: 'column',
sortOrder: 'asc',
customizeText(cellInfo) {
return cellInfo.value + '月';
}
}
16.5 为什么字段拖动后刷新页面仍然是旧布局?
因为启用了 DxStateStoring。
清理:
js
localStorage.removeItem('retail-sale-pivotgrid-analysis-state-v1')
或者升级 key:
vue
<DxStateStoring
:enabled="true"
type="localStorage"
storage-key="retail-sale-pivotgrid-analysis-state-v2"
/>
17. ERP 销售分析推荐字段设计
17.1 后端返回字段
json
{
"statYear": 2026,
"statMonth": 1,
"monthLabel": "1月",
"khdm": "SD001",
"khmc": "直营1店",
"saleAmount": 100000.00,
"saleNum": 120,
"zbje": 80000.00,
"mlje": 20000.00,
"mlzbje": 15000.00
}
17.2 推荐维度
| 类型 | 字段 |
|---|---|
| 行维度 | 客户、门店、品牌、商品分类、季节 |
| 列维度 | 年、月、季度 |
| 数据指标 | 销售金额、销售数量、销售指标、毛利金额、毛利指标、达成率 |
| 筛选字段 | 渠道、区域、业务员、客户类型 |
17.3 推荐指标
| 指标 | 计算方式 |
|---|---|
| 销售金额 | sum(saleAmount) |
| 销售数量 | sum(saleNum) |
| 销售指标 | sum(zbje) |
| 毛利金额 | sum(mlje) |
| 毛利指标 | sum(mlzbje) |
| 销售达成率 | 销售金额 / 销售指标 |
| 毛利达成率 | 毛利金额 / 毛利指标 |
18. 自定义指标:销售达成率
销售达成率不能简单 avg,应该按汇总结果计算:
js
{
caption: '销售达成率',
dataType: 'number',
area: 'data',
calculateSummaryValue(summaryCell) {
const saleAmount = summaryCell.value('销售金额') || 0;
const targetAmount = summaryCell.value('销售指标') || 0;
if (targetAmount === 0) {
return 0;
}
return saleAmount / targetAmount;
},
format: {
type: 'percent',
precision: 2
}
}
注意:
summaryCell.value('销售金额')里的名称要与对应 data 字段的caption一致。
19. 自定义汇总:平均单价
平均单价应使用:
text
销售金额汇总 / 销售数量汇总
不建议使用 avg(price)。
js
{
caption: '平均单价',
area: 'data',
dataType: 'number',
summaryType: 'custom',
calculateCustomSummary(options) {
if (options.summaryProcess === 'start') {
options.totalValue = {
amount: 0,
num: 0
};
}
if (options.summaryProcess === 'calculate') {
options.totalValue.amount += Number(options.value.saleAmount || 0);
options.totalValue.num += Number(options.value.saleNum || 0);
}
if (options.summaryProcess === 'finalize') {
const amount = options.totalValue.amount;
const num = options.totalValue.num;
options.totalValue = num === 0 ? 0 : amount / num;
}
},
format: {
type: 'fixedPoint',
precision: 2
}
}
20. 项目落地建议
20.1 前端原则
- 所有金额、数量、指标字段统一
dataType: 'number' - 所有数据指标统一配置
summaryType - 多指标透视表加
dataFieldArea: 'column' - 数据加载后统一
Number()转换 - 字段还在调整时关闭
DxStateStoring - 字段稳定后再开启状态保存,并给
storage-key加版本号
20.2 后端原则
- 金额字段统一返回数字,不要返回字符串
- 字段名与前端
dataField完全一致 - 月份最好同时返回
monthNum和monthLabel - 指标字段即使没有值,也建议返回
0,不要缺字段 - SQL 别名建议统一 camelCase 或 snake_case,不要混用
推荐 SQL 返回字段:
sql
select
kh.khdm as khdm,
kh.khmc as khmc,
month(model.rq) as monthNum,
cast(month(model.rq) as varchar(2)) + '月' as monthLabel,
sum(model.je) as saleAmount,
sum(model.sl) as saleNum,
sum(coalesce(model.zbje, 0)) as zbje,
sum(coalesce(model.mlje, 0)) as mlje,
sum(coalesce(model.mlzbje, 0)) as mlzbje
from xxx model
left join kehu kh on model.khdm = kh.khdm
group by kh.khdm, kh.khmc, month(model.rq)
21. 推荐最终模板
js
const buildPivotDataSource = rows => ({
fields: [
{
caption: '客户名称',
dataField: 'khmc',
area: 'row',
width: 140,
expanded: true
},
{
caption: '客户代码',
dataField: 'khdm',
area: 'row',
width: 100,
expanded: true
},
{
caption: '月份',
dataField: 'monthNum',
area: 'column',
sortOrder: 'asc',
customizeText(cellInfo) {
return cellInfo.value + '月';
}
},
{
caption: '销售金额',
dataField: 'saleAmount',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: { type: 'fixedPoint', precision: 2 }
},
{
caption: '销售指标',
dataField: 'zbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: { type: 'fixedPoint', precision: 2 }
},
{
caption: '毛利金额',
dataField: 'mlje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: { type: 'fixedPoint', precision: 2 }
},
{
caption: '毛利指标',
dataField: 'mlzbje',
dataType: 'number',
area: 'data',
summaryType: 'sum',
format: { type: 'fixedPoint', precision: 2 }
}
],
dataFieldArea: 'column',
store: rows.map(item => ({
...item,
saleAmount: Number(item.saleAmount || 0),
zbje: Number(item.zbje || 0),
mlje: Number(item.mlje || 0),
mlzbje: Number(item.mlzbje || 0)
}))
});
使用:
js
dataSource.value = buildPivotDataSource(result.data);
22. 排查清单
当 PivotGrid 显示异常时,按这个顺序排查:
text
1. console.log(result.data[0]),确认字段名存在
2. 检查字段大小写是否一致
3. data 字段是否配置 dataType: 'number'
4. data 字段是否配置 summaryType: 'sum'
5. 金额是否是字符串,需要 Number() 转换
6. 多个 data 字段是否配置 dataFieldArea: 'column'
7. 是否启用 DxStateStoring,是否需要清 localStorage
8. 是否所有业务数据本身就是 0
9. 月份是否字符串排序异常
10. 是否整体替换 dataSource,而不是只改 dataSource.value.store
23. 官方资料
- DevExtreme Vue PivotGrid API: https://js.devexpress.com/Vue/Documentation/ApiReference/UI_Components/dxPivotGrid/
- PivotGrid fields and areas: https://js.devexpress.com/Vue/Documentation/Guide/UI_Components/PivotGrid/Fields_and_Areas/
- PivotGrid stateStoring: https://js.devexpress.com/Vue/Documentation/ApiReference/UI_Components/dxPivotGrid/Configuration/stateStoring/
- PivotGrid export: https://js.devexpress.com/Vue/Documentation/ApiReference/UI_Components/dxPivotGrid/Configuration/export/