feat(视图-透视表): 支持透视表
This commit is contained in:
parent
2342eb240e
commit
b8f2f4fad0
@ -263,7 +263,7 @@ const active = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const boardMoveActive = computed(() => {
|
const boardMoveActive = computed(() => {
|
||||||
return ['map', 'table-info', 'table-normal'].includes(element.value.innerType)
|
return ['map', 'table-info', 'table-normal', 'table-pivot'].includes(element.value.innerType)
|
||||||
})
|
})
|
||||||
|
|
||||||
const dashboardActive = computed(() => {
|
const dashboardActive = computed(() => {
|
||||||
|
|||||||
@ -275,7 +275,7 @@ declare interface TotalConfig {
|
|||||||
/**
|
/**
|
||||||
* 小计维度
|
* 小计维度
|
||||||
*/
|
*/
|
||||||
subTotalsDimensions: []
|
subTotalsDimensions: string[]
|
||||||
/**
|
/**
|
||||||
* 总计汇总设置
|
* 总计汇总设置
|
||||||
*/
|
*/
|
||||||
@ -297,7 +297,17 @@ declare interface TotalConfig {
|
|||||||
* 汇总聚合方式
|
* 汇总聚合方式
|
||||||
*/
|
*/
|
||||||
declare interface CalcTotals {
|
declare interface CalcTotals {
|
||||||
aggregation: string
|
aggregation: 'MIN' | 'MAX' | 'AVG' | 'SUM'
|
||||||
|
cfg: CalcTotalCfg[]
|
||||||
|
calcFunc?: (...args) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇总聚合配置
|
||||||
|
*/
|
||||||
|
declare interface CalcTotalCfg {
|
||||||
|
dataeaseName: string
|
||||||
|
aggregation: 'MIN' | 'MAX' | 'AVG' | 'SUM' | ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -82,12 +82,10 @@ watch(
|
|||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
const AXIS_FORMAT_VIEW = ['table-normal', 'table-info', 'table-pivot', 'indicator']
|
||||||
const showValueFormatter = computed<boolean>(() => {
|
const showValueFormatter = computed<boolean>(() => {
|
||||||
return (
|
return (
|
||||||
(props.chart.type === 'table-normal' ||
|
AXIS_FORMAT_VIEW.includes(props.chart.type) &&
|
||||||
props.chart.type === 'table-info' ||
|
|
||||||
props.chart.type === 'indicator') &&
|
|
||||||
(props.item.deType === 2 || props.item.deType === 3)
|
(props.item.deType === 2 || props.item.deType === 3)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,582 @@
|
|||||||
<script setup lang="ts"></script>
|
<script lang="ts" setup>
|
||||||
|
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { DEFAULT_TABLE_TOTAL } from '@/views/chart/components/editor/util/chart'
|
||||||
|
import { cloneDeep, defaultsDeep } from 'lodash-es'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
chart: {
|
||||||
|
type: Object as PropType<ChartObj>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
themes: {
|
||||||
|
type: String as PropType<EditorTheme>,
|
||||||
|
default: 'dark'
|
||||||
|
},
|
||||||
|
propertyInner: {
|
||||||
|
type: Array<string>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(
|
||||||
|
[props.chart.customAttr.tableTotal, props.chart.yAxis],
|
||||||
|
() => {
|
||||||
|
init()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const fontSizeList = computed(() => {
|
||||||
|
const arr = []
|
||||||
|
for (let i = 10; i <= 40; i = i + 2) {
|
||||||
|
arr.push({
|
||||||
|
name: i + '',
|
||||||
|
value: i
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
})
|
||||||
|
const aggregations = [
|
||||||
|
{ name: t('chart.sum'), value: 'SUM' },
|
||||||
|
{ name: t('chart.avg'), value: 'AVG' },
|
||||||
|
{ name: t('chart.max'), value: 'MAX' },
|
||||||
|
{ name: t('chart.min'), value: 'MIN' }
|
||||||
|
]
|
||||||
|
const state = reactive({
|
||||||
|
tableTotalForm: cloneDeep(DEFAULT_TABLE_TOTAL) as ChartTableTotalAttr,
|
||||||
|
rowSubTotalItem: {
|
||||||
|
dataeaseName: '',
|
||||||
|
aggregation: ''
|
||||||
|
} as CalcTotalCfg,
|
||||||
|
rowTotalItem: {
|
||||||
|
dataeaseName: '',
|
||||||
|
aggregation: ''
|
||||||
|
} as CalcTotalCfg,
|
||||||
|
colSubTotalItem: {
|
||||||
|
dataeaseName: '',
|
||||||
|
aggregation: ''
|
||||||
|
} as CalcTotalCfg,
|
||||||
|
colTotalItem: {
|
||||||
|
dataeaseName: '',
|
||||||
|
aggregation: ''
|
||||||
|
} as CalcTotalCfg
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['onTableTotalChange'])
|
||||||
|
|
||||||
|
const changeTableTotal = prop => {
|
||||||
|
emit('onTableTotalChange', state.tableTotalForm, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const tableTotal = props.chart?.customAttr?.tableTotal
|
||||||
|
if (tableTotal) {
|
||||||
|
state.tableTotalForm = defaultsDeep(cloneDeep(tableTotal), cloneDeep(DEFAULT_TABLE_TOTAL))
|
||||||
|
}
|
||||||
|
const yAxis = props.chart.yAxis
|
||||||
|
if (yAxis?.length > 0) {
|
||||||
|
const axisArr = yAxis.map(i => i.dataeaseName)
|
||||||
|
if (axisArr.indexOf(state.tableTotalForm.row.totalSortField) != -1) {
|
||||||
|
state.tableTotalForm.row.totalSortField = yAxis[0].dataeaseName
|
||||||
|
}
|
||||||
|
state.tableTotalForm.col.totalSortField = yAxis[0].dataeaseName
|
||||||
|
} else {
|
||||||
|
state.tableTotalForm.row.totalSortField = ''
|
||||||
|
state.tableTotalForm.col.totalSortField = ''
|
||||||
|
}
|
||||||
|
const totals = [
|
||||||
|
{ ...state.tableTotalForm.row.calcTotals },
|
||||||
|
{ ...state.tableTotalForm.row.calcSubTotals },
|
||||||
|
{ ...state.tableTotalForm.col.calcTotals },
|
||||||
|
{ ...state.tableTotalForm.col.calcSubTotals }
|
||||||
|
]
|
||||||
|
totals.forEach(total => {
|
||||||
|
setupTotalCfg(total.cfg, yAxis)
|
||||||
|
})
|
||||||
|
const totalTupleArr: [CalcTotalCfg, CalcTotalCfg[]][] = [
|
||||||
|
[state.rowTotalItem, state.tableTotalForm.row.calcTotals.cfg],
|
||||||
|
[state.rowSubTotalItem, state.tableTotalForm.row.calcSubTotals.cfg],
|
||||||
|
[state.colTotalItem, state.tableTotalForm.col.calcTotals.cfg],
|
||||||
|
[state.colSubTotalItem, state.tableTotalForm.col.calcSubTotals.cfg]
|
||||||
|
]
|
||||||
|
totalTupleArr.forEach(tuple => {
|
||||||
|
const [total, totalCfg] = tuple
|
||||||
|
if (!totalCfg.length) {
|
||||||
|
total.dataeaseName = ''
|
||||||
|
total.aggregation = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const totalIndex = totalCfg.findIndex(i => i.dataeaseName === total.dataeaseName)
|
||||||
|
if (totalIndex !== -1) {
|
||||||
|
total.aggregation = totalCfg[totalIndex].aggregation
|
||||||
|
} else {
|
||||||
|
total.dataeaseName = totalCfg[0].dataeaseName
|
||||||
|
total.aggregation = totalCfg[0].aggregation
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||||
|
const changeTotal = (totalItem, totals) => {
|
||||||
|
for (let i = 0; i < totals.length; i++) {
|
||||||
|
const item = totals[i]
|
||||||
|
if (item.dataeaseName === totalItem.dataeaseName) {
|
||||||
|
totalItem.aggregation = item.aggregation
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changeTotalAggr = (totalItem, totals, colOrNum) => {
|
||||||
|
for (let i = 0; i < totals.length; i++) {
|
||||||
|
const item = totals[i]
|
||||||
|
if (item.dataeaseName === totalItem.dataeaseName) {
|
||||||
|
item.aggregation = totalItem.aggregation
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeTableTotal(colOrNum)
|
||||||
|
}
|
||||||
|
const setupTotalCfg = (totalCfg, axis) => {
|
||||||
|
if (!totalCfg.length) {
|
||||||
|
axis.forEach(i => {
|
||||||
|
totalCfg.push({
|
||||||
|
dataeaseName: i.dataeaseName,
|
||||||
|
aggregation: 'SUM'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!axis.length) {
|
||||||
|
totalCfg.splice(0, totalCfg.length)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const cfgMap = totalCfg.reduce((p, n) => {
|
||||||
|
p[n.dataeaseName] = n
|
||||||
|
return p
|
||||||
|
}, {})
|
||||||
|
totalCfg.splice(0, totalCfg.length)
|
||||||
|
axis.forEach(i => {
|
||||||
|
totalCfg.push({
|
||||||
|
dataeaseName: i.dataeaseName,
|
||||||
|
aggregation: cfgMap[i.dataeaseName] ? cfgMap[i.dataeaseName].aggregation : 'SUM'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span>to be implement</span>
|
<el-form ref="tableTotalForm" :model="state.tableTotalForm" label-position="top">
|
||||||
|
<el-divider v-if="showProperty('row')" content-position="center" class="divider-style">
|
||||||
|
{{ t('chart.row_cfg') }}
|
||||||
|
</el-divider>
|
||||||
|
<el-form-item
|
||||||
|
v-show="showProperty('row')"
|
||||||
|
:label="t('chart.total_show')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="state.tableTotalForm.row.showGrandTotals"
|
||||||
|
@change="changeTableTotal('row.showGrandTotals')"
|
||||||
|
>
|
||||||
|
{{ t('chart.show') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-show="showProperty('row') && state.tableTotalForm.row.showGrandTotals">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_position')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.row.reverseLayout"
|
||||||
|
@change="changeTableTotal('row.reverseLayout')"
|
||||||
|
>
|
||||||
|
<el-radio :label="true">{{ t('chart.total_pos_top') }}</el-radio>
|
||||||
|
<el-radio :label="false">{{ t('chart.total_pos_bottom') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_label')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="state.tableTotalForm.row.label"
|
||||||
|
:placeholder="t('chart.total_label')"
|
||||||
|
clearable
|
||||||
|
@change="changeTableTotal('row.label')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.aggregation')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-select
|
||||||
|
v-model="state.rowTotalItem.dataeaseName"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="changeTotal(state.rowTotalItem, state.tableTotalForm.row.calcTotals.cfg)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="11" :offset="2">
|
||||||
|
<el-select
|
||||||
|
v-model="state.rowTotalItem.aggregation"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="
|
||||||
|
changeTotalAggr(
|
||||||
|
state.rowTotalItem,
|
||||||
|
state.tableTotalForm.row.calcTotals.cfg,
|
||||||
|
'row.calcTotals.cfg'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in aggregations"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="chart.type === 'table-pivot'"
|
||||||
|
:label="t('chart.total_sort')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.row.totalSort"
|
||||||
|
@change="changeTableTotal('row.totalSort')"
|
||||||
|
>
|
||||||
|
<el-radio label="none">{{ t('chart.total_sort_none') }}</el-radio>
|
||||||
|
<el-radio label="asc">{{ t('chart.total_sort_asc') }}</el-radio>
|
||||||
|
<el-radio label="desc">{{ t('chart.total_sort_desc') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="chart.type === 'table-pivot' && state.tableTotalForm.row.totalSort !== 'none'"
|
||||||
|
:label="t('chart.total_sort_field')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="state.tableTotalForm.row.totalSortField"
|
||||||
|
class="form-item-select"
|
||||||
|
:placeholder="t('chart.total_sort_field')"
|
||||||
|
@change="changeTableTotal('row')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-show="showProperty('row')"
|
||||||
|
:label="t('chart.sub_total_show')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="state.tableTotalForm.row.showSubTotals"
|
||||||
|
:disabled="chart.xAxisExt.length < 2"
|
||||||
|
@change="changeTableTotal('row')"
|
||||||
|
>{{ t('chart.show') }}</el-checkbox
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-show="showProperty('row') && state.tableTotalForm.row.showSubTotals">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_position')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.row.reverseSubLayout"
|
||||||
|
:disabled="chart.xAxisExt.length < 2"
|
||||||
|
@change="changeTableTotal('row')"
|
||||||
|
>
|
||||||
|
<el-radio :label="true">{{ t('chart.total_pos_top') }}</el-radio>
|
||||||
|
<el-radio :label="false">{{ t('chart.total_pos_bottom') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_label')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="state.tableTotalForm.row.subLabel"
|
||||||
|
:disabled="chart.xAxisExt.length < 2"
|
||||||
|
:placeholder="t('chart.total_label')"
|
||||||
|
clearable
|
||||||
|
@change="changeTableTotal"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.aggregation')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-select
|
||||||
|
v-model="state.rowSubTotalItem.dataeaseName"
|
||||||
|
:disabled="chart.xAxisExt.length < 2"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="changeTotal(state.rowSubTotalItem, state.tableTotalForm.row.calcSubTotals.cfg)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="11" :offset="2">
|
||||||
|
<el-select
|
||||||
|
v-model="state.rowSubTotalItem.aggregation"
|
||||||
|
:disabled="chart.xAxisExt.length < 2"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="
|
||||||
|
changeTotalAggr(
|
||||||
|
state.rowSubTotalItem,
|
||||||
|
state.tableTotalForm.row.calcSubTotals.cfg,
|
||||||
|
'row'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in aggregations"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-divider v-if="showProperty('col')" content-position="center" class="divider-style">{{
|
||||||
|
t('chart.col_cfg')
|
||||||
|
}}</el-divider>
|
||||||
|
<el-form-item
|
||||||
|
v-show="showProperty('col')"
|
||||||
|
:label="t('chart.total_show')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="state.tableTotalForm.col.showGrandTotals"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>{{ t('chart.show') }}</el-checkbox
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-show="showProperty('col') && state.tableTotalForm.col.showGrandTotals">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_position')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.col.reverseLayout"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>
|
||||||
|
<el-radio :label="true">{{ t('chart.total_pos_left') }}</el-radio>
|
||||||
|
<el-radio :label="false">{{ t('chart.total_pos_right') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_label')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="state.tableTotalForm.col.label"
|
||||||
|
:placeholder="t('chart.total_label')"
|
||||||
|
clearable
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.aggregation')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-select
|
||||||
|
v-model="state.colTotalItem.dataeaseName"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="changeTotal(state.colTotalItem, state.tableTotalForm.col.calcTotals.cfg)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="11" :offset="2">
|
||||||
|
<el-select
|
||||||
|
v-model="state.colTotalItem.aggregation"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="
|
||||||
|
changeTotalAggr(
|
||||||
|
state.colTotalItem,
|
||||||
|
state.tableTotalForm.col.calcTotals.cfg,
|
||||||
|
'col.calcTotals.cfg'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in aggregations"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="chart.type === 'table-pivot'"
|
||||||
|
:label="t('chart.total_sort')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.col.totalSort"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>
|
||||||
|
<el-radio label="none">{{ t('chart.total_sort_none') }}</el-radio>
|
||||||
|
<el-radio label="asc">{{ t('chart.total_sort_asc') }}</el-radio>
|
||||||
|
<el-radio label="desc">{{ t('chart.total_sort_desc') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-show="
|
||||||
|
false && chart.type === 'table-pivot' && state.tableTotalForm.col?.totalSort !== 'none'
|
||||||
|
"
|
||||||
|
:label="t('chart.total_sort_field')"
|
||||||
|
class="form-item"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="state.tableTotalForm.col.totalSortField"
|
||||||
|
class="form-item-select"
|
||||||
|
:placeholder="t('chart.total_sort_field')"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
v-show="showProperty('col')"
|
||||||
|
:label="t('chart.sub_total_show')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="state.tableTotalForm.col.showSubTotals"
|
||||||
|
:disabled="chart.xAxis.length < 2"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>{{ t('chart.show') }}</el-checkbox
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-show="showProperty('col') && state.tableTotalForm.col.showSubTotals">
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_position')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="state.tableTotalForm.col.reverseSubLayout"
|
||||||
|
:disabled="chart.xAxis?.length < 2"
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
>
|
||||||
|
<el-radio :label="true">{{ t('chart.total_pos_left') }}</el-radio>
|
||||||
|
<el-radio :label="false">{{ t('chart.total_pos_right') }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.total_label')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="state.tableTotalForm.col.subLabel"
|
||||||
|
:disabled="chart.xAxis?.length < 2"
|
||||||
|
:placeholder="t('chart.total_label')"
|
||||||
|
clearable
|
||||||
|
@change="changeTableTotal('col')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:label="t('chart.aggregation')"
|
||||||
|
class="form-item"
|
||||||
|
:class="'form-item-' + themes"
|
||||||
|
>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-select
|
||||||
|
v-model="state.colSubTotalItem.dataeaseName"
|
||||||
|
:disabled="chart.xAxis?.length < 2"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="changeTotal(state.colSubTotalItem, state.tableTotalForm.col.calcSubTotals.cfg)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in chart.yAxis"
|
||||||
|
:key="option.dataeaseName"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.dataeaseName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="11" :offset="2">
|
||||||
|
<el-select
|
||||||
|
v-model="state.colSubTotalItem.aggregation"
|
||||||
|
:disabled="chart.xAxis?.length < 2"
|
||||||
|
:placeholder="t('chart.aggregation')"
|
||||||
|
@change="
|
||||||
|
changeTotalAggr(
|
||||||
|
state.colSubTotalItem,
|
||||||
|
state.tableTotalForm.col.calcSubTotals.cfg,
|
||||||
|
'col.calcSubTotals.cfg'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in aggregations"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.name"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="less"></style>
|
|
||||||
|
<style scoped></style>
|
||||||
|
|||||||
@ -289,10 +289,12 @@ export const DEFAULT_TABLE_TOTAL: ChartTableTotalAttr = {
|
|||||||
subLabel: '小计',
|
subLabel: '小计',
|
||||||
subTotalsDimensions: [],
|
subTotalsDimensions: [],
|
||||||
calcTotals: {
|
calcTotals: {
|
||||||
aggregation: 'SUM'
|
aggregation: 'SUM',
|
||||||
|
cfg: []
|
||||||
},
|
},
|
||||||
calcSubTotals: {
|
calcSubTotals: {
|
||||||
aggregation: 'SUM'
|
aggregation: 'SUM',
|
||||||
|
cfg: []
|
||||||
},
|
},
|
||||||
totalSort: 'none',
|
totalSort: 'none',
|
||||||
totalSortField: ''
|
totalSortField: ''
|
||||||
@ -306,10 +308,12 @@ export const DEFAULT_TABLE_TOTAL: ChartTableTotalAttr = {
|
|||||||
subLabel: '小计',
|
subLabel: '小计',
|
||||||
subTotalsDimensions: [],
|
subTotalsDimensions: [],
|
||||||
calcTotals: {
|
calcTotals: {
|
||||||
aggregation: 'SUM'
|
aggregation: 'SUM',
|
||||||
|
cfg: []
|
||||||
},
|
},
|
||||||
calcSubTotals: {
|
calcSubTotals: {
|
||||||
aggregation: 'SUM'
|
aggregation: 'SUM',
|
||||||
|
cfg: []
|
||||||
},
|
},
|
||||||
totalSort: 'none', // asc,desc
|
totalSort: 'none', // asc,desc
|
||||||
totalSortField: ''
|
totalSortField: ''
|
||||||
@ -1013,6 +1017,13 @@ export const CHART_TYPE_CONFIGS = [
|
|||||||
value: 'table-normal',
|
value: 'table-normal',
|
||||||
title: t('chart.chart_table_normal'),
|
title: t('chart.chart_table_normal'),
|
||||||
icon: 'table-normal'
|
icon: 'table-normal'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
render: 'antv',
|
||||||
|
category: 'table',
|
||||||
|
value: 'table-pivot',
|
||||||
|
title: t('chart.chart_table_pivot'),
|
||||||
|
icon: 'table-pivot'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,337 @@
|
|||||||
|
import { EXTRA_FIELD, PivotSheet, S2Event, S2Options, TOTAL_VALUE } from '@antv/s2/esm/index'
|
||||||
|
import { formatterItem, valueFormatter } from '../../../formatter'
|
||||||
|
import { hexColorToRGBA, parseJson } from '../../../util'
|
||||||
|
import { S2ChartView, S2DrawOptions } from '../../types/impl/s2'
|
||||||
|
import { TABLE_EDITOR_PROPERTY_INNER } from './common'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { maxBy, merge, minBy } from 'lodash-es'
|
||||||
|
import { S2Theme } from '@antv/s2'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 透视表
|
||||||
|
*/
|
||||||
|
export class TablePivot extends S2ChartView<PivotSheet> {
|
||||||
|
properties: EditorProperty[] = [
|
||||||
|
'background-overall-component',
|
||||||
|
'basic-style-selector',
|
||||||
|
'table-header-selector',
|
||||||
|
'table-cell-selector',
|
||||||
|
'table-total-selector',
|
||||||
|
'title-selector',
|
||||||
|
'function-cfg',
|
||||||
|
'threshold',
|
||||||
|
'linkage',
|
||||||
|
'jump-set'
|
||||||
|
]
|
||||||
|
propertyInner = {
|
||||||
|
...TABLE_EDITOR_PROPERTY_INNER,
|
||||||
|
'table-header-selector': [
|
||||||
|
'tableHeaderBgColor',
|
||||||
|
'tableTitleFontSize',
|
||||||
|
'tableHeaderFontColor',
|
||||||
|
'tableTitleHeight',
|
||||||
|
'tableHeaderAlign'
|
||||||
|
],
|
||||||
|
'table-total-selector': ['row', 'col'],
|
||||||
|
'basic-style-selector': ['tableColumnMode', 'tableBorderColor', 'tableScrollBarColor', 'alpha']
|
||||||
|
}
|
||||||
|
axis: AxisType[] = ['xAxis', 'xAxisExt', 'yAxis', 'filter']
|
||||||
|
axisConfig: AxisConfig = {
|
||||||
|
xAxis: {
|
||||||
|
name: `${t('chart.table_pivot_row')} / ${t('chart.dimension')}`,
|
||||||
|
type: 'd'
|
||||||
|
},
|
||||||
|
xAxisExt: {
|
||||||
|
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.dimension')}`,
|
||||||
|
type: 'd'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.quota')}`,
|
||||||
|
type: 'q'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public drawChart(drawOption: S2DrawOptions<PivotSheet>): PivotSheet {
|
||||||
|
const { container, chart, chartObj, action } = drawOption
|
||||||
|
const containerDom = document.getElementById(container)
|
||||||
|
|
||||||
|
const { xAxis: columnFields, xAxisExt: rowFields, yAxis: valueFields } = chart
|
||||||
|
const [c, r, v] = [columnFields, rowFields, valueFields].map(arr =>
|
||||||
|
arr.map(i => i.dataeaseName)
|
||||||
|
)
|
||||||
|
|
||||||
|
// fields
|
||||||
|
const fields = chart.data.fields
|
||||||
|
if (!fields || fields.length === 0) {
|
||||||
|
if (chartObj) {
|
||||||
|
chartObj.destroy()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = []
|
||||||
|
const meta = []
|
||||||
|
|
||||||
|
const valueFieldMap: Record<string, Axis> = chart.yAxis.reduce((p, n) => {
|
||||||
|
p[n.dataeaseName] = n
|
||||||
|
return p
|
||||||
|
}, {})
|
||||||
|
fields.forEach(ele => {
|
||||||
|
const f = valueFieldMap[ele.dataeaseName]
|
||||||
|
columns.push(ele.dataeaseName)
|
||||||
|
meta.push({
|
||||||
|
field: ele.dataeaseName,
|
||||||
|
name: ele.name,
|
||||||
|
formatter: value => {
|
||||||
|
if (!f) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (f.formatterCfg) {
|
||||||
|
return valueFormatter(value, f.formatterCfg)
|
||||||
|
} else {
|
||||||
|
return valueFormatter(value, formatterItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// total config
|
||||||
|
const customAttr = parseJson(chart.customAttr)
|
||||||
|
const { tableTotal } = customAttr
|
||||||
|
tableTotal.row.subTotalsDimensions = r
|
||||||
|
tableTotal.col.subTotalsDimensions = c
|
||||||
|
|
||||||
|
// 解析合计、小计排序
|
||||||
|
const sortParams = []
|
||||||
|
if (
|
||||||
|
tableTotal.row.totalSort &&
|
||||||
|
tableTotal.row.totalSort !== 'none' &&
|
||||||
|
c.length > 0 &&
|
||||||
|
tableTotal.row.showGrandTotals &&
|
||||||
|
v.indexOf(tableTotal.row.totalSortField) > -1
|
||||||
|
) {
|
||||||
|
const sort = {
|
||||||
|
sortFieldId: c[0],
|
||||||
|
sortMethod: tableTotal.row.totalSort.toUpperCase(),
|
||||||
|
sortByMeasure: TOTAL_VALUE,
|
||||||
|
query: {
|
||||||
|
[EXTRA_FIELD]: tableTotal.row.totalSortField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortParams.push(sort)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
tableTotal.col.totalSort &&
|
||||||
|
tableTotal.col.totalSort !== 'none' &&
|
||||||
|
r.length > 0 &&
|
||||||
|
tableTotal.col.showGrandTotals &&
|
||||||
|
v.indexOf(tableTotal.col.totalSortField) > -1
|
||||||
|
) {
|
||||||
|
const sort = {
|
||||||
|
sortFieldId: r[0],
|
||||||
|
sortMethod: tableTotal.col.totalSort.toUpperCase(),
|
||||||
|
sortByMeasure: TOTAL_VALUE,
|
||||||
|
query: {
|
||||||
|
[EXTRA_FIELD]: tableTotal.col.totalSortField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortParams.push(sort)
|
||||||
|
}
|
||||||
|
// 自定义总计小计
|
||||||
|
const totals = [
|
||||||
|
tableTotal.row.calcTotals,
|
||||||
|
tableTotal.row.calcSubTotals,
|
||||||
|
tableTotal.col.calcTotals,
|
||||||
|
tableTotal.col.calcSubTotals
|
||||||
|
]
|
||||||
|
totals.forEach(total => {
|
||||||
|
if (total.cfg?.length) {
|
||||||
|
delete total.aggregation
|
||||||
|
const totalCfgMap = total.cfg.reduce((p, n) => {
|
||||||
|
p[n.dataeaseName] = n
|
||||||
|
return p
|
||||||
|
}, {})
|
||||||
|
total.calcFunc = (query, data) => {
|
||||||
|
return customCalcFunc(query, data, totalCfgMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 空值处理
|
||||||
|
const newData = this.configEmptyDataStrategy(chart)
|
||||||
|
// data config
|
||||||
|
const s2DataConfig = {
|
||||||
|
fields: {
|
||||||
|
rows: r,
|
||||||
|
columns: c,
|
||||||
|
values: v
|
||||||
|
},
|
||||||
|
meta: meta,
|
||||||
|
data: newData,
|
||||||
|
sortParams: sortParams
|
||||||
|
}
|
||||||
|
// options
|
||||||
|
const s2Options = {
|
||||||
|
width: containerDom.offsetWidth,
|
||||||
|
height: containerDom.offsetHeight,
|
||||||
|
style: this.configStyle(chart),
|
||||||
|
totals: tableTotal,
|
||||||
|
conditions: this.configConditions(chart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始渲染
|
||||||
|
const s2 = new PivotSheet(containerDom, s2DataConfig, s2Options as unknown as S2Options)
|
||||||
|
|
||||||
|
// click
|
||||||
|
s2.on(S2Event.DATA_CELL_CLICK, ev => this.dataCellClickAction(chart, ev, s2, action))
|
||||||
|
s2.on(S2Event.ROW_CELL_CLICK, ev => this.headerCellClickAction(chart, ev, s2, action))
|
||||||
|
s2.on(S2Event.COL_CELL_CLICK, ev => this.headerCellClickAction(chart, ev, s2, action))
|
||||||
|
|
||||||
|
// theme
|
||||||
|
const customTheme = this.configTheme(chart)
|
||||||
|
s2.setThemeCfg({ theme: customTheme })
|
||||||
|
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
private dataCellClickAction(chart: Chart, ev, s2Instance: PivotSheet, callback) {
|
||||||
|
const cell = s2Instance.getCell(ev.target)
|
||||||
|
const meta = cell.getMeta()
|
||||||
|
const nameIdMap = chart.data.fields.reduce((pre, next) => {
|
||||||
|
pre[next['dataeaseName']] = next['id']
|
||||||
|
return pre
|
||||||
|
}, {})
|
||||||
|
const rowData = { ...meta.rowQuery, ...meta.colQuery }
|
||||||
|
rowData[meta.valueField] = meta.fieldValue
|
||||||
|
const dimensionList = []
|
||||||
|
for (const key in rowData) {
|
||||||
|
if (nameIdMap[key]) {
|
||||||
|
dimensionList.push({ id: nameIdMap[key], value: rowData[key] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const param = {
|
||||||
|
x: ev.x,
|
||||||
|
y: ev.y,
|
||||||
|
data: {
|
||||||
|
dimensionList,
|
||||||
|
name: nameIdMap[meta.valueField],
|
||||||
|
sourceType: 'table-pivot',
|
||||||
|
quotaList: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(param)
|
||||||
|
}
|
||||||
|
private headerCellClickAction(chart: Chart, ev, s2Instance: PivotSheet, callback) {
|
||||||
|
const cell = s2Instance.getCell(ev.target)
|
||||||
|
const meta = cell.getMeta()
|
||||||
|
const rowData = meta.query
|
||||||
|
const nameIdMap = chart.data.fields.reduce((pre, next) => {
|
||||||
|
pre[next['dataeaseName']] = next['id']
|
||||||
|
return pre
|
||||||
|
}, {})
|
||||||
|
const dimensionList = []
|
||||||
|
for (const key in rowData) {
|
||||||
|
if (nameIdMap[key]) {
|
||||||
|
dimensionList.push({ id: nameIdMap[key], value: rowData[key] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const param = {
|
||||||
|
x: ev.x,
|
||||||
|
y: ev.y,
|
||||||
|
data: {
|
||||||
|
dimensionList,
|
||||||
|
name: nameIdMap[meta.valueField],
|
||||||
|
sourceType: 'table-pivot',
|
||||||
|
quotaList: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(param)
|
||||||
|
}
|
||||||
|
protected configTheme(chart: Chart): S2Theme {
|
||||||
|
const theme = super.configTheme(chart)
|
||||||
|
const { basicStyle, tableHeader } = parseJson(chart.customAttr)
|
||||||
|
const tableHeaderBgColor = hexColorToRGBA(tableHeader.tableHeaderBgColor, basicStyle.alpha)
|
||||||
|
const tableBorderColor = hexColorToRGBA(basicStyle.tableBorderColor, basicStyle.alpha)
|
||||||
|
const tableHeaderFontColor = hexColorToRGBA(tableHeader.tableHeaderFontColor, basicStyle.alpha)
|
||||||
|
const pivotTheme = {
|
||||||
|
cornerCell: {
|
||||||
|
cell: {
|
||||||
|
verticalBorderWidth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowCell: {
|
||||||
|
cell: {
|
||||||
|
backgroundColor: tableHeaderBgColor,
|
||||||
|
horizontalBorderColor: tableBorderColor,
|
||||||
|
verticalBorderColor: tableBorderColor
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fill: tableHeaderFontColor,
|
||||||
|
fontSize: tableHeader.tableTitleFontSize,
|
||||||
|
textAlign: tableHeader.tableHeaderAlign,
|
||||||
|
textBaseline: 'top'
|
||||||
|
},
|
||||||
|
bolderText: {
|
||||||
|
fill: tableHeaderFontColor,
|
||||||
|
fontSize: tableHeader.tableTitleFontSize,
|
||||||
|
textAlign: tableHeader.tableHeaderAlign
|
||||||
|
},
|
||||||
|
measureText: {
|
||||||
|
fill: tableHeaderFontColor,
|
||||||
|
fontSize: tableHeader.tableTitleFontSize,
|
||||||
|
textAlign: tableHeader.tableHeaderAlign
|
||||||
|
},
|
||||||
|
seriesText: {
|
||||||
|
fill: tableHeaderFontColor,
|
||||||
|
fontSize: tableHeader.tableTitleFontSize,
|
||||||
|
textAlign: tableHeader.tableHeaderAlign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merge(theme, pivotTheme)
|
||||||
|
return theme
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('table-pivot', [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function customCalcFunc(query, data, totalCfgMap) {
|
||||||
|
if (!data?.length) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const aggregation = totalCfgMap[query[EXTRA_FIELD]].aggregation
|
||||||
|
switch (aggregation) {
|
||||||
|
case 'SUM': {
|
||||||
|
return data.reduce((p, n) => {
|
||||||
|
return p + n[n[EXTRA_FIELD]]
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
case 'AVG': {
|
||||||
|
const sum = data.reduce((p, n) => {
|
||||||
|
return p + n[n[EXTRA_FIELD]]
|
||||||
|
}, 0)
|
||||||
|
return sum / data.length
|
||||||
|
}
|
||||||
|
case 'MIN': {
|
||||||
|
const result = minBy(data, n => {
|
||||||
|
return n[n[EXTRA_FIELD]]
|
||||||
|
})
|
||||||
|
return result[result[EXTRA_FIELD]]
|
||||||
|
}
|
||||||
|
case 'MAX': {
|
||||||
|
const result = maxBy(data, n => {
|
||||||
|
return n[n[EXTRA_FIELD]]
|
||||||
|
})
|
||||||
|
return result[result[EXTRA_FIELD]]
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return data.reduce((p, n) => {
|
||||||
|
return p + n[n[EXTRA_FIELD]]
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user