mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-03-07 13:48:31 +08:00
feat(data-table): multiple column sorting (#1035)
* feat(data-table): multiple sorter * fix(data-table): cell color while sorting * feat(data-table): multiple sort finished * feat: add test * feat(data-table): sort uncontrolled test * feat(data-table): finish test * feat(data-table): add controlled mutiple sort * chore(data-table): clean code * optimization * optimization * optimization * optimization * optimization Co-authored-by: Jiwen Bai <56228105@qq.com> Co-authored-by: 07akioni <07akioni2@gmail.com>
This commit is contained in:
parent
f19822bdd1
commit
8fcf0558f6
130
src/data-table/demos/zhCN/controlled-multiple-sorter.demo.md
Normal file
130
src/data-table/demos/zhCN/controlled-multiple-sorter.demo.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# 受控的多列排序
|
||||||
|
|
||||||
|
如果列对象的 sortOrder 属性被设为 'ascend'、'descend' 或者 false,表格的排序将为受控状态。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<n-space vertical :size="12">
|
||||||
|
<n-data-table
|
||||||
|
ref="table"
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:pagination="pagination"
|
||||||
|
@update:sorter="handleUpdateSorter"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
name: 'John Brown',
|
||||||
|
age: 32,
|
||||||
|
address: 'New York No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 60,
|
||||||
|
english: 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
name: 'Jim Green',
|
||||||
|
age: 42,
|
||||||
|
address: 'London No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
name: 'Joe Black',
|
||||||
|
age: 32,
|
||||||
|
address: 'Sidney No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
name: 'Jim Red',
|
||||||
|
age: 32,
|
||||||
|
address: 'London No. 2 Lake Park',
|
||||||
|
chinese: 88,
|
||||||
|
math: 99,
|
||||||
|
english: 89
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
pagination: { pageSize: 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup () {
|
||||||
|
const sortStatesRef = ref([])
|
||||||
|
const sortKeyMapOrderRef = computed(() =>
|
||||||
|
sortStatesRef.value.reduce((result, { columnKey, order }) => {
|
||||||
|
result[columnKey] = order
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
)
|
||||||
|
const paginationRef = ref({ pageSize: 5 })
|
||||||
|
|
||||||
|
const columnsRef = computed(() => [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
key: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
key: 'age',
|
||||||
|
sortOrder: sortKeyMapOrderRef.value.age || false,
|
||||||
|
sorter (rowA, rowB) {
|
||||||
|
return rowA.age - rowB.age
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Chinese Score',
|
||||||
|
key: 'chinese',
|
||||||
|
sortOrder: sortKeyMapOrderRef.value.chinese || false,
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.chinese - b.chinese,
|
||||||
|
multiple: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Math Score',
|
||||||
|
key: 'math',
|
||||||
|
sortOrder: sortKeyMapOrderRef.value.math || false,
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.math - b.math,
|
||||||
|
multiple: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'English Score',
|
||||||
|
sortOrder: sortKeyMapOrderRef.value.english || false,
|
||||||
|
key: 'english',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.english - b.english,
|
||||||
|
multiple: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
function handleUpdateSorter (sorters) {
|
||||||
|
console.log(sorters)
|
||||||
|
sortStatesRef.value = [].concat(sorters)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
columns: columnsRef,
|
||||||
|
handleUpdateSorter,
|
||||||
|
data,
|
||||||
|
pagination: paginationRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -19,12 +19,14 @@ size
|
|||||||
row-props
|
row-props
|
||||||
merge-cell
|
merge-cell
|
||||||
filter-and-sorter
|
filter-and-sorter
|
||||||
|
multiple-sorter
|
||||||
select
|
select
|
||||||
custom-select
|
custom-select
|
||||||
group-header
|
group-header
|
||||||
controlled-page
|
controlled-page
|
||||||
controlled-filter
|
controlled-filter
|
||||||
controlled-sorter
|
controlled-sorter
|
||||||
|
controlled-multiple-sorter
|
||||||
fixed-header
|
fixed-header
|
||||||
fixed-header-column
|
fixed-header-column
|
||||||
summary
|
summary
|
||||||
|
143
src/data-table/demos/zhCN/multiple-sorter.demo.md
Normal file
143
src/data-table/demos/zhCN/multiple-sorter.demo.md
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# 多列排序
|
||||||
|
|
||||||
|
如果仅想使用多列排序的 UI,`sorter` 不传 `compare` 函数即可。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<n-space vertical :size="12">
|
||||||
|
<n-space>
|
||||||
|
<n-button @click="sortName">Sort By Name (Ascend)</n-button>
|
||||||
|
<n-button @click="filterAddress">Filter Address (London)</n-button>
|
||||||
|
<n-button @click="clearFilters">Clear Filters</n-button>
|
||||||
|
<n-button @click="clearSorter">Clear Sorter</n-button>
|
||||||
|
</n-space>
|
||||||
|
<n-data-table
|
||||||
|
ref="table"
|
||||||
|
:columns="columns"
|
||||||
|
:data="data"
|
||||||
|
:pagination="pagination"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
key: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Age',
|
||||||
|
key: 'age',
|
||||||
|
sorter: (row1, row2) => row1.age - row2.age
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Chinese Score',
|
||||||
|
key: 'chinese',
|
||||||
|
defaultSortOrder: false,
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.chinese - b.chinese,
|
||||||
|
multiple: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Math Score',
|
||||||
|
defaultSortOrder: false,
|
||||||
|
key: 'math',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.math - b.math,
|
||||||
|
multiple: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'English Score',
|
||||||
|
defaultSortOrder: false,
|
||||||
|
key: 'english',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.english - b.english,
|
||||||
|
multiple: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Address',
|
||||||
|
key: 'address',
|
||||||
|
filterOptions: [
|
||||||
|
{
|
||||||
|
label: 'London',
|
||||||
|
value: 'London'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'New York',
|
||||||
|
value: 'New York'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filter (value, row) {
|
||||||
|
return ~row.address.indexOf(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
name: 'John Brown',
|
||||||
|
age: 32,
|
||||||
|
address: 'New York No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 60,
|
||||||
|
english: 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
name: 'Jim Green',
|
||||||
|
age: 42,
|
||||||
|
address: 'London No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
name: 'Joe Black',
|
||||||
|
age: 32,
|
||||||
|
address: 'Sidney No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
name: 'Jim Red',
|
||||||
|
age: 32,
|
||||||
|
address: 'London No. 2 Lake Park',
|
||||||
|
chinese: 88,
|
||||||
|
math: 99,
|
||||||
|
english: 89
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
columns,
|
||||||
|
pagination: { pageSize: 5 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
filterAddress () {
|
||||||
|
this.$refs.table.filter({
|
||||||
|
address: ['London']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sortName () {
|
||||||
|
this.$refs.table.sort('name', 'ascend')
|
||||||
|
},
|
||||||
|
clearFilters () {
|
||||||
|
this.$refs.table.filter(null)
|
||||||
|
},
|
||||||
|
clearSorter () {
|
||||||
|
this.$refs.table.sort(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -19,15 +19,19 @@ export default defineComponent({
|
|||||||
const { mergedSortStateRef, mergedClsPrefixRef } = inject(
|
const { mergedSortStateRef, mergedClsPrefixRef } = inject(
|
||||||
dataTableInjectionKey
|
dataTableInjectionKey
|
||||||
)!
|
)!
|
||||||
const sortStateRef = mergedSortStateRef
|
const sortStateRef = computed(() =>
|
||||||
|
mergedSortStateRef.value.find(
|
||||||
|
(state) => state.columnKey === props.column.key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const activeRef = computed(() => {
|
const activeRef = computed(() => {
|
||||||
const { value } = sortStateRef
|
return sortStateRef.value !== undefined
|
||||||
if (value) return value.columnKey === props.column.key
|
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
const mergedSortOrderRef = computed(() => {
|
const mergedSortOrderRef = computed(() => {
|
||||||
const { value } = sortStateRef
|
if (sortStateRef.value && activeRef.value) {
|
||||||
if (value) return activeRef.value ? value.order : false
|
return sortStateRef.value.order
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
const mergedRenderSorterRef = computed(() => {
|
const mergedRenderSorterRef = computed(() => {
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
MainTableBodyRef,
|
MainTableBodyRef,
|
||||||
TmNode
|
TmNode
|
||||||
} from '../interface'
|
} from '../interface'
|
||||||
import { createRowClassName, getColKey } from '../utils'
|
import { createRowClassName, getColKey, isColumnSorting } from '../utils'
|
||||||
import Cell from './Cell'
|
import Cell from './Cell'
|
||||||
import ExpandTrigger from './ExpandTrigger'
|
import ExpandTrigger from './ExpandTrigger'
|
||||||
import RenderSafeCheckbox from './BodyCheckbox'
|
import RenderSafeCheckbox from './BodyCheckbox'
|
||||||
@ -384,10 +384,6 @@ export default defineComponent({
|
|||||||
paginatedData.forEach((tmNode, rowIndex) => {
|
paginatedData.forEach((tmNode, rowIndex) => {
|
||||||
rowIndexToKey[rowIndex] = tmNode.key
|
rowIndexToKey[rowIndex] = tmNode.key
|
||||||
})
|
})
|
||||||
const sorterKey =
|
|
||||||
!!mergedSortState &&
|
|
||||||
mergedSortState.order &&
|
|
||||||
mergedSortState.columnKey
|
|
||||||
|
|
||||||
let mergedData: RowRenderInfo[]
|
let mergedData: RowRenderInfo[]
|
||||||
|
|
||||||
@ -503,7 +499,7 @@ export default defineComponent({
|
|||||||
isSummary && `${mergedClsPrefix}-data-table-td--summary`,
|
isSummary && `${mergedClsPrefix}-data-table-td--summary`,
|
||||||
((hoverKey !== null &&
|
((hoverKey !== null &&
|
||||||
cordKey[rowIndex][colIndex].includes(hoverKey)) ||
|
cordKey[rowIndex][colIndex].includes(hoverKey)) ||
|
||||||
(sorterKey !== false && sorterKey === colKey)) &&
|
isColumnSorting(column, mergedSortState)) &&
|
||||||
`${mergedClsPrefix}-data-table-td--hover`,
|
`${mergedClsPrefix}-data-table-td--hover`,
|
||||||
column.fixed &&
|
column.fixed &&
|
||||||
`${mergedClsPrefix}-data-table-td--fixed-${column.fixed}`,
|
`${mergedClsPrefix}-data-table-td--fixed-${column.fixed}`,
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
isColumnSortable,
|
isColumnSortable,
|
||||||
isColumnFilterable,
|
isColumnFilterable,
|
||||||
createNextSorter,
|
createNextSorter,
|
||||||
getColKey
|
getColKey,
|
||||||
|
isColumnSorting
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import {
|
import {
|
||||||
TableExpandColumn,
|
TableExpandColumn,
|
||||||
@ -72,7 +73,10 @@ export default defineComponent({
|
|||||||
): void {
|
): void {
|
||||||
if (happensIn(e, 'dataTableFilter')) return
|
if (happensIn(e, 'dataTableFilter')) return
|
||||||
if (!isColumnSortable(column)) return
|
if (!isColumnSortable(column)) return
|
||||||
const activeSorter = mergedSortStateRef.value
|
const activeSorter =
|
||||||
|
mergedSortStateRef.value.find(
|
||||||
|
(state) => state.columnKey === column.key
|
||||||
|
) || null
|
||||||
const nextSorter = createNextSorter(column, activeSorter)
|
const nextSorter = createNextSorter(column, activeSorter)
|
||||||
doUpdateSorter(nextSorter)
|
doUpdateSorter(nextSorter)
|
||||||
}
|
}
|
||||||
@ -109,7 +113,6 @@ export default defineComponent({
|
|||||||
currentPage,
|
currentPage,
|
||||||
allRowsChecked,
|
allRowsChecked,
|
||||||
someRowsChecked,
|
someRowsChecked,
|
||||||
mergedSortState,
|
|
||||||
rows,
|
rows,
|
||||||
cols,
|
cols,
|
||||||
mergedTheme,
|
mergedTheme,
|
||||||
@ -118,6 +121,7 @@ export default defineComponent({
|
|||||||
discrete,
|
discrete,
|
||||||
mergedTableLayout,
|
mergedTableLayout,
|
||||||
headerCheckboxDisabled,
|
headerCheckboxDisabled,
|
||||||
|
mergedSortState,
|
||||||
handleColHeaderClick,
|
handleColHeaderClick,
|
||||||
handleCheckboxUpdateChecked
|
handleCheckboxUpdateChecked
|
||||||
} = this
|
} = this
|
||||||
@ -151,8 +155,7 @@ export default defineComponent({
|
|||||||
`${mergedClsPrefix}-data-table-th--fixed-${column.fixed}`,
|
`${mergedClsPrefix}-data-table-th--fixed-${column.fixed}`,
|
||||||
{
|
{
|
||||||
[`${mergedClsPrefix}-data-table-th--hover`]:
|
[`${mergedClsPrefix}-data-table-th--hover`]:
|
||||||
mergedSortState?.order &&
|
isColumnSorting(column, mergedSortState),
|
||||||
mergedSortState.columnKey === key,
|
|
||||||
[`${mergedClsPrefix}-data-table-th--filterable`]:
|
[`${mergedClsPrefix}-data-table-th--filterable`]:
|
||||||
isColumnFilterable(column),
|
isColumnFilterable(column),
|
||||||
[`${mergedClsPrefix}-data-table-th--sortable`]:
|
[`${mergedClsPrefix}-data-table-th--sortable`]:
|
||||||
|
@ -34,8 +34,13 @@ export type CreateRowProps<T = InternalRowData> = (
|
|||||||
row: T,
|
row: T,
|
||||||
index: number
|
index: number
|
||||||
) => HTMLAttributes
|
) => HTMLAttributes
|
||||||
|
export type CompareFn<T = InternalRowData> = (row1: T, row2: T) => number
|
||||||
|
export type Sorter<T = InternalRowData> = CompareFn<T> | SorterMultiple<T>
|
||||||
|
export interface SorterMultiple<T = InternalRowData> {
|
||||||
|
multiple: number
|
||||||
|
compare?: CompareFn<T> | 'default'
|
||||||
|
}
|
||||||
|
|
||||||
export type Sorter<T = InternalRowData> = (row1: T, row2: T) => number
|
|
||||||
export type Filter<T = InternalRowData> = (
|
export type Filter<T = InternalRowData> = (
|
||||||
filterOptionValue: FilterOptionValue,
|
filterOptionValue: FilterOptionValue,
|
||||||
row: T
|
row: T
|
||||||
@ -174,7 +179,7 @@ export interface DataTableInjection {
|
|||||||
mergedCurrentPageRef: Ref<number>
|
mergedCurrentPageRef: Ref<number>
|
||||||
someRowsCheckedRef: Ref<boolean>
|
someRowsCheckedRef: Ref<boolean>
|
||||||
allRowsCheckedRef: Ref<boolean>
|
allRowsCheckedRef: Ref<boolean>
|
||||||
mergedSortStateRef: Ref<SortState | null>
|
mergedSortStateRef: Ref<SortState[]>
|
||||||
mergedFilterStateRef: Ref<FilterState>
|
mergedFilterStateRef: Ref<FilterState>
|
||||||
loadingRef: Ref<boolean>
|
loadingRef: Ref<boolean>
|
||||||
rowClassNameRef: Ref<string | CreateRowClassName | undefined>
|
rowClassNameRef: Ref<string | CreateRowClassName | undefined>
|
||||||
@ -231,7 +236,7 @@ export type RenderFilterMenu = (actions: { hide: () => void }) => VNodeChild
|
|||||||
|
|
||||||
export type OnUpdateExpandedRowKeys = (keys: RowKey[]) => void
|
export type OnUpdateExpandedRowKeys = (keys: RowKey[]) => void
|
||||||
export type OnUpdateCheckedRowKeys = (keys: RowKey[]) => void
|
export type OnUpdateCheckedRowKeys = (keys: RowKey[]) => void
|
||||||
export type OnUpdateSorter = (sortState: SortState | null) => void
|
export type OnUpdateSorter = (sortState: SortState | SortState[] | null) => void
|
||||||
export type OnUpdateFilters = (
|
export type OnUpdateFilters = (
|
||||||
filterState: FilterState,
|
filterState: FilterState,
|
||||||
sourceColumn?: TableBaseColumn
|
sourceColumn?: TableBaseColumn
|
||||||
|
250
src/data-table/src/use-sorter.ts
Normal file
250
src/data-table/src/use-sorter.ts
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import { computed, ref, ComputedRef } from 'vue'
|
||||||
|
import {
|
||||||
|
ColumnKey,
|
||||||
|
InternalRowData,
|
||||||
|
SortOrder,
|
||||||
|
SortState,
|
||||||
|
TmNode,
|
||||||
|
TableBaseColumn,
|
||||||
|
TableExpandColumn,
|
||||||
|
TableSelectionColumn,
|
||||||
|
CompareFn
|
||||||
|
} from './interface'
|
||||||
|
import { getFlagOfOrder } from './utils'
|
||||||
|
import { call } from '../../_utils'
|
||||||
|
import type { DataTableSetupProps } from './DataTable'
|
||||||
|
|
||||||
|
function getMultiplePriority ({
|
||||||
|
sorter
|
||||||
|
}: {
|
||||||
|
sorter: TableBaseColumn['sorter']
|
||||||
|
}): number | false {
|
||||||
|
if (typeof sorter === 'object' && typeof sorter.multiple === 'number') {
|
||||||
|
return sorter.multiple
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortFunction (
|
||||||
|
sorter: TableBaseColumn['sorter'],
|
||||||
|
columnKey: ColumnKey
|
||||||
|
): CompareFn | false {
|
||||||
|
if (
|
||||||
|
columnKey &&
|
||||||
|
(sorter === undefined ||
|
||||||
|
sorter === 'default' ||
|
||||||
|
(typeof sorter === 'object' && sorter.compare === 'default'))
|
||||||
|
) {
|
||||||
|
return getDefaultSorterFn(columnKey)
|
||||||
|
}
|
||||||
|
if (typeof sorter === 'function') {
|
||||||
|
return sorter
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
sorter &&
|
||||||
|
typeof sorter === 'object' &&
|
||||||
|
sorter.compare &&
|
||||||
|
sorter.compare !== 'default'
|
||||||
|
) {
|
||||||
|
return sorter.compare
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
function getDefaultSorterFn (columnKey: ColumnKey) {
|
||||||
|
return (row1: InternalRowData, row2: InternalRowData) => {
|
||||||
|
const value1 = row1[columnKey]
|
||||||
|
const value2 = row2[columnKey]
|
||||||
|
|
||||||
|
if (typeof value1 === 'number' && typeof value2 === 'number') {
|
||||||
|
return value1 - value2
|
||||||
|
} else if (typeof value1 === 'string' && typeof value2 === 'string') {
|
||||||
|
return value1.localeCompare(value2)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
export function useSorter (
|
||||||
|
props: DataTableSetupProps,
|
||||||
|
{
|
||||||
|
dataRelatedColsRef,
|
||||||
|
filteredDataRef
|
||||||
|
}: {
|
||||||
|
dataRelatedColsRef: ComputedRef<
|
||||||
|
Array<TableSelectionColumn | TableBaseColumn | TableExpandColumn>
|
||||||
|
>
|
||||||
|
filteredDataRef: ComputedRef<TmNode[]>
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const uncontrolledSortStateRef = ref<SortState[]>([])
|
||||||
|
const mergedSortStateRef = computed(() => {
|
||||||
|
// If one of the columns's sort order is false or 'ascend' or 'descend',
|
||||||
|
// the table's controll functionality should work in controlled manner.
|
||||||
|
const columnsWithControlledSortOrder = dataRelatedColsRef.value.filter(
|
||||||
|
(column) =>
|
||||||
|
column.type !== 'selection' &&
|
||||||
|
column.sorter !== undefined &&
|
||||||
|
(column.sortOrder === 'ascend' ||
|
||||||
|
column.sortOrder === 'descend' ||
|
||||||
|
column.sortOrder === false)
|
||||||
|
)
|
||||||
|
// if multiple columns are controlled sortable, then we need to find columns with active sortOrder
|
||||||
|
const columnToSort: TableBaseColumn[] | undefined = (
|
||||||
|
columnsWithControlledSortOrder as TableBaseColumn[]
|
||||||
|
).filter((col: TableBaseColumn) => col.sortOrder !== false)
|
||||||
|
if (columnToSort.length) {
|
||||||
|
return columnToSort.map((column) => {
|
||||||
|
return {
|
||||||
|
columnKey: column.key,
|
||||||
|
// column to sort has controlled sorter
|
||||||
|
// sorter && sort order won't be undefined
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
order: column.sortOrder!,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
sorter: column.sorter!
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (columnsWithControlledSortOrder.length) return []
|
||||||
|
return uncontrolledSortStateRef.value
|
||||||
|
})
|
||||||
|
const sortedDataRef = computed<TmNode[]>(() => {
|
||||||
|
const activeSorters = mergedSortStateRef.value.slice().sort((a, b) => {
|
||||||
|
const item1Priority = getMultiplePriority(a) || 0
|
||||||
|
const item2Priority = getMultiplePriority(b) || 0
|
||||||
|
return item2Priority - item1Priority
|
||||||
|
})
|
||||||
|
if (activeSorters.length) {
|
||||||
|
const filteredData = filteredDataRef.value.slice()
|
||||||
|
return filteredData.sort((tmNode1, tmNode2) => {
|
||||||
|
let compareResult = 0
|
||||||
|
activeSorters.some((sorterState) => {
|
||||||
|
const { columnKey, sorter, order } = sorterState
|
||||||
|
|
||||||
|
const compareFn = getSortFunction(sorter, columnKey)
|
||||||
|
if (compareFn && order) {
|
||||||
|
compareResult = compareFn(tmNode1.rawNode, tmNode2.rawNode)
|
||||||
|
|
||||||
|
if (compareResult !== 0) {
|
||||||
|
compareResult = compareResult * getFlagOfOrder(order)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return compareResult
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return filteredDataRef.value
|
||||||
|
})
|
||||||
|
|
||||||
|
dataRelatedColsRef.value.forEach((column) => {
|
||||||
|
if (column.sorter !== undefined) {
|
||||||
|
addSortSate({
|
||||||
|
columnKey: column.key,
|
||||||
|
sorter: column.sorter,
|
||||||
|
order: column.defaultSortOrder ?? false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function getUpdatedSorterState (
|
||||||
|
sortState: SortState | null
|
||||||
|
): SortState | null | SortState[] {
|
||||||
|
let currentSortState = mergedSortStateRef.value.slice()
|
||||||
|
// Multiple sorter
|
||||||
|
if (
|
||||||
|
sortState &&
|
||||||
|
getMultiplePriority({ sorter: sortState.sorter }) !== false
|
||||||
|
) {
|
||||||
|
// clear column is not multiple sort
|
||||||
|
currentSortState = currentSortState.filter(
|
||||||
|
(sortState) =>
|
||||||
|
getMultiplePriority({ sorter: sortState.sorter }) !== false
|
||||||
|
)
|
||||||
|
updateSortInSortStates(currentSortState, sortState)
|
||||||
|
return currentSortState
|
||||||
|
} else if (sortState) {
|
||||||
|
// single sorter
|
||||||
|
return sortState
|
||||||
|
}
|
||||||
|
// no sorter
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function doUpdateSorter (sortState: SortState | null): void {
|
||||||
|
const {
|
||||||
|
'onUpdate:sorter': _onUpdateSorter,
|
||||||
|
onUpdateSorter,
|
||||||
|
onSorterChange
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const updateSorterState: SortState | SortState[] | null =
|
||||||
|
getUpdatedSorterState(sortState)
|
||||||
|
|
||||||
|
if (_onUpdateSorter) call(_onUpdateSorter, updateSorterState)
|
||||||
|
if (onUpdateSorter) call(onUpdateSorter, updateSorterState)
|
||||||
|
if (onSorterChange) call(onSorterChange, updateSorterState)
|
||||||
|
if (Array.isArray(updateSorterState)) {
|
||||||
|
uncontrolledSortStateRef.value = updateSorterState
|
||||||
|
} else if (updateSorterState) {
|
||||||
|
uncontrolledSortStateRef.value = [updateSorterState]
|
||||||
|
} else {
|
||||||
|
uncontrolledSortStateRef.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort (columnKey: ColumnKey, order: SortOrder = 'ascend'): void {
|
||||||
|
if (!columnKey) {
|
||||||
|
clearSorter()
|
||||||
|
} else {
|
||||||
|
const columnToSort = dataRelatedColsRef.value.find(
|
||||||
|
(column) =>
|
||||||
|
column.type !== 'selection' &&
|
||||||
|
column.type !== 'expand' &&
|
||||||
|
column.key === columnKey
|
||||||
|
)
|
||||||
|
if (!columnToSort || !columnToSort.sorter) return
|
||||||
|
const sorter = columnToSort.sorter
|
||||||
|
doUpdateSorter({
|
||||||
|
columnKey,
|
||||||
|
sorter,
|
||||||
|
order: order
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSorter (): void {
|
||||||
|
doUpdateSorter(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSortInSortStates (
|
||||||
|
sortStates: SortState[],
|
||||||
|
sortState: SortState
|
||||||
|
): void {
|
||||||
|
const index = sortStates.findIndex(
|
||||||
|
(state) => sortState?.columnKey && state.columnKey === sortState.columnKey
|
||||||
|
)
|
||||||
|
if (index !== undefined && index >= 0) {
|
||||||
|
sortStates[index] = sortState
|
||||||
|
} else {
|
||||||
|
sortStates.push(sortState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSortSate (sortState: SortState): void {
|
||||||
|
updateSortInSortStates(uncontrolledSortStateRef.value, sortState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clearSorter,
|
||||||
|
sort,
|
||||||
|
sortedDataRef,
|
||||||
|
mergedSortStateRef,
|
||||||
|
uncontrolledSortStateRef,
|
||||||
|
doUpdateSorter
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import {
|
|||||||
FilterOptionValue,
|
FilterOptionValue,
|
||||||
FilterState,
|
FilterState,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
SortState,
|
|
||||||
TableBaseColumn,
|
TableBaseColumn,
|
||||||
TableSelectionColumn,
|
TableSelectionColumn,
|
||||||
InternalRowData,
|
InternalRowData,
|
||||||
@ -16,10 +15,10 @@ import {
|
|||||||
TableExpandColumn,
|
TableExpandColumn,
|
||||||
RowKey
|
RowKey
|
||||||
} from './interface'
|
} from './interface'
|
||||||
import { createShallowClonedObject, getFlagOfOrder } from './utils'
|
import { createShallowClonedObject } from './utils'
|
||||||
import { PaginationProps } from '../../pagination/src/Pagination'
|
import { PaginationProps } from '../../pagination/src/Pagination'
|
||||||
import { call, warn } from '../../_utils'
|
import { call, warn } from '../../_utils'
|
||||||
|
import { useSorter } from './use-sorter'
|
||||||
// useTableData combines filter, sorter and pagination
|
// useTableData combines filter, sorter and pagination
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
@ -70,92 +69,9 @@ export function useTableData (
|
|||||||
})
|
})
|
||||||
|
|
||||||
const uncontrolledFilterStateRef = ref<FilterState>({})
|
const uncontrolledFilterStateRef = ref<FilterState>({})
|
||||||
const uncontrolledSortStateRef = ref<SortState | null>(null)
|
|
||||||
const uncontrolledCurrentPageRef = ref(1)
|
const uncontrolledCurrentPageRef = ref(1)
|
||||||
const uncontrolledPageSizeRef = ref(10)
|
const uncontrolledPageSizeRef = ref(10)
|
||||||
|
|
||||||
dataRelatedColsRef.value.forEach((column) => {
|
|
||||||
if (column.sorter !== undefined) {
|
|
||||||
uncontrolledSortStateRef.value = {
|
|
||||||
columnKey: column.key,
|
|
||||||
sorter: column.sorter,
|
|
||||||
order: column.defaultSortOrder ?? false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (column.filter) {
|
|
||||||
const defaultFilterOptionValues = column.defaultFilterOptionValues
|
|
||||||
if (column.filterMultiple) {
|
|
||||||
uncontrolledFilterStateRef.value[column.key] =
|
|
||||||
defaultFilterOptionValues || []
|
|
||||||
} else if (defaultFilterOptionValues !== undefined) {
|
|
||||||
// this branch is for compatibility, someone may use `values` in single filter mode
|
|
||||||
uncontrolledFilterStateRef.value[column.key] =
|
|
||||||
defaultFilterOptionValues === null ? [] : defaultFilterOptionValues
|
|
||||||
} else {
|
|
||||||
uncontrolledFilterStateRef.value[column.key] =
|
|
||||||
column.defaultFilterOptionValue ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const controlledCurrentPageRef = computed(() => {
|
|
||||||
const { pagination } = props
|
|
||||||
if (pagination === false) return undefined
|
|
||||||
return pagination.page
|
|
||||||
})
|
|
||||||
const controlledPageSizeRef = computed(() => {
|
|
||||||
const { pagination } = props
|
|
||||||
if (pagination === false) return undefined
|
|
||||||
return pagination.pageSize
|
|
||||||
})
|
|
||||||
|
|
||||||
const mergedCurrentPageRef = useMergedState(
|
|
||||||
controlledCurrentPageRef,
|
|
||||||
uncontrolledCurrentPageRef
|
|
||||||
)
|
|
||||||
const mergedPageSizeRef = useMergedState(
|
|
||||||
controlledPageSizeRef,
|
|
||||||
uncontrolledPageSizeRef
|
|
||||||
)
|
|
||||||
const mergedPageCountRef = computed(() => {
|
|
||||||
const { pagination } = props
|
|
||||||
if (pagination) {
|
|
||||||
const { pageCount } = pagination
|
|
||||||
if (pageCount !== undefined) return pageCount
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
const mergedSortStateRef = computed<SortState | null>(() => {
|
|
||||||
// If one of the columns's sort order is false or 'ascend' or 'descend',
|
|
||||||
// the table's controll functionality should work in controlled manner.
|
|
||||||
const columnsWithControlledSortOrder = dataRelatedColsRef.value.filter(
|
|
||||||
(column) =>
|
|
||||||
column.type !== 'selection' &&
|
|
||||||
column.sorter !== undefined &&
|
|
||||||
(column.sortOrder === 'ascend' ||
|
|
||||||
column.sortOrder === 'descend' ||
|
|
||||||
column.sortOrder === false)
|
|
||||||
)
|
|
||||||
// if multiple column is controlled sortable, then we need to find a column with active sortOrder
|
|
||||||
const columnToSort: TableBaseColumn | undefined = (
|
|
||||||
columnsWithControlledSortOrder as TableBaseColumn[]
|
|
||||||
).filter((col: TableBaseColumn) => col.sortOrder !== false)[0]
|
|
||||||
if (columnToSort) {
|
|
||||||
return {
|
|
||||||
columnKey: columnToSort.key,
|
|
||||||
// column to sort has controlled sorter
|
|
||||||
// sorter && sort order won't be undefined
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
order: columnToSort.sortOrder!,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
sorter: columnToSort.sorter!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (columnsWithControlledSortOrder.length) return null
|
|
||||||
return uncontrolledSortStateRef.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const mergedFilterStateRef = computed<FilterState>(() => {
|
const mergedFilterStateRef = computed<FilterState>(() => {
|
||||||
const columnsWithControlledFilter = dataRelatedColsRef.value.filter(
|
const columnsWithControlledFilter = dataRelatedColsRef.value.filter(
|
||||||
(column) => {
|
(column) => {
|
||||||
@ -242,41 +158,56 @@ export function useTableData (
|
|||||||
: []
|
: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const sortedDataRef = computed<TmNode[]>(() => {
|
const { sortedDataRef, doUpdateSorter, mergedSortStateRef } = useSorter(
|
||||||
const activeSorter = mergedSortStateRef.value
|
props,
|
||||||
if (activeSorter) {
|
{
|
||||||
// When async, mergedSortState.sorter should be true
|
dataRelatedColsRef,
|
||||||
// and we sort nothing, just return the filtered data
|
filteredDataRef
|
||||||
if (activeSorter.sorter === true || activeSorter.sorter === false) {
|
|
||||||
return filteredDataRef.value
|
|
||||||
}
|
|
||||||
const filteredData = filteredDataRef.value.slice(0)
|
|
||||||
const columnKey = activeSorter.columnKey
|
|
||||||
// 1 for asc
|
|
||||||
// -1 for desc
|
|
||||||
const order = activeSorter.order
|
|
||||||
const sorter =
|
|
||||||
activeSorter.sorter === undefined || activeSorter.sorter === 'default'
|
|
||||||
? (row1: InternalRowData, row2: InternalRowData) => {
|
|
||||||
const value1 = row1[columnKey]
|
|
||||||
const value2 = row2[columnKey]
|
|
||||||
if (typeof value1 === 'number' && typeof value2 === 'number') {
|
|
||||||
return value1 - value2
|
|
||||||
} else if (
|
|
||||||
typeof value1 === 'string' &&
|
|
||||||
typeof value2 === 'string'
|
|
||||||
) {
|
|
||||||
return value1.localeCompare(value2)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
: activeSorter.sorter
|
|
||||||
return filteredData.sort(
|
|
||||||
(tmNode1, tmNode2) =>
|
|
||||||
getFlagOfOrder(order) * sorter(tmNode1.rawNode, tmNode2.rawNode)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return filteredDataRef.value
|
)
|
||||||
|
dataRelatedColsRef.value.forEach((column) => {
|
||||||
|
if (column.filter) {
|
||||||
|
const defaultFilterOptionValues = column.defaultFilterOptionValues
|
||||||
|
if (column.filterMultiple) {
|
||||||
|
uncontrolledFilterStateRef.value[column.key] =
|
||||||
|
defaultFilterOptionValues || []
|
||||||
|
} else if (defaultFilterOptionValues !== undefined) {
|
||||||
|
// this branch is for compatibility, someone may use `values` in single filter mode
|
||||||
|
uncontrolledFilterStateRef.value[column.key] =
|
||||||
|
defaultFilterOptionValues === null ? [] : defaultFilterOptionValues
|
||||||
|
} else {
|
||||||
|
uncontrolledFilterStateRef.value[column.key] =
|
||||||
|
column.defaultFilterOptionValue ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const controlledCurrentPageRef = computed(() => {
|
||||||
|
const { pagination } = props
|
||||||
|
if (pagination === false) return undefined
|
||||||
|
return pagination.page
|
||||||
|
})
|
||||||
|
const controlledPageSizeRef = computed(() => {
|
||||||
|
const { pagination } = props
|
||||||
|
if (pagination === false) return undefined
|
||||||
|
return pagination.pageSize
|
||||||
|
})
|
||||||
|
|
||||||
|
const mergedCurrentPageRef = useMergedState(
|
||||||
|
controlledCurrentPageRef,
|
||||||
|
uncontrolledCurrentPageRef
|
||||||
|
)
|
||||||
|
const mergedPageSizeRef = useMergedState(
|
||||||
|
controlledPageSizeRef,
|
||||||
|
uncontrolledPageSizeRef
|
||||||
|
)
|
||||||
|
const mergedPageCountRef = computed(() => {
|
||||||
|
const { pagination } = props
|
||||||
|
if (pagination) {
|
||||||
|
const { pageCount } = pagination
|
||||||
|
if (pageCount !== undefined) return pageCount
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const paginatedDataRef = computed<TmNode[]>(() => {
|
const paginatedDataRef = computed<TmNode[]>(() => {
|
||||||
@ -365,17 +296,7 @@ export function useTableData (
|
|||||||
if (_onUpdatePageSize) call(_onUpdatePageSize, pageSize)
|
if (_onUpdatePageSize) call(_onUpdatePageSize, pageSize)
|
||||||
uncontrolledPageSizeRef.value = pageSize
|
uncontrolledPageSizeRef.value = pageSize
|
||||||
}
|
}
|
||||||
function doUpdateSorter (sortState: SortState | null): void {
|
|
||||||
const {
|
|
||||||
'onUpdate:sorter': _onUpdateSorter,
|
|
||||||
onUpdateSorter,
|
|
||||||
onSorterChange
|
|
||||||
} = props
|
|
||||||
if (_onUpdateSorter) call(_onUpdateSorter, sortState)
|
|
||||||
if (onUpdateSorter) call(onUpdateSorter, sortState)
|
|
||||||
if (onSorterChange) call(onSorterChange, sortState)
|
|
||||||
uncontrolledSortStateRef.value = sortState
|
|
||||||
}
|
|
||||||
function doUpdateFilters (
|
function doUpdateFilters (
|
||||||
filters: FilterState,
|
filters: FilterState,
|
||||||
sourceColumn?: TableBaseColumn
|
sourceColumn?: TableBaseColumn
|
||||||
@ -397,6 +318,7 @@ export function useTableData (
|
|||||||
if (!columnKey) {
|
if (!columnKey) {
|
||||||
clearSorter()
|
clearSorter()
|
||||||
} else {
|
} else {
|
||||||
|
// TODO:
|
||||||
const columnToSort = dataRelatedColsRef.value.find(
|
const columnToSort = dataRelatedColsRef.value.find(
|
||||||
(column) =>
|
(column) =>
|
||||||
column.type !== 'selection' &&
|
column.type !== 'selection' &&
|
||||||
|
@ -108,3 +108,15 @@ export function createNextSorter (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isColumnSorting (
|
||||||
|
column: TableColumn,
|
||||||
|
mergedSortState: SortState[]
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
mergedSortState.find(
|
||||||
|
(state) =>
|
||||||
|
state.columnKey === (column as TableBaseColumn).key && state.order
|
||||||
|
) !== undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { h, HTMLAttributes, nextTick } from 'vue'
|
import { h, HTMLAttributes, nextTick, ref } from 'vue'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { NDataTable } from '../index'
|
import { DataTableInst, NDataTable } from '../index'
|
||||||
import type { DataTableColumns } from '../index'
|
import type { DataTableColumns } from '../index'
|
||||||
import { NButton, NButtonGroup } from '../../button'
|
import { NButton, NButtonGroup } from '../../button'
|
||||||
|
|
||||||
@ -319,6 +319,267 @@ describe('n-data-table', () => {
|
|||||||
expect(wrapper.find('tbody .n-data-table-tr').classes()).toContain('0-test')
|
expect(wrapper.find('tbody .n-data-table-tr').classes()).toContain('0-test')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('should work with multiple sorter', () => {
|
||||||
|
interface UserData {
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
address: string
|
||||||
|
chinese: number
|
||||||
|
math: number
|
||||||
|
english: number
|
||||||
|
}
|
||||||
|
const columns: DataTableColumns<UserData> = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
key: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => h('span', { id: 'age-title' }, 'Age'),
|
||||||
|
className: 'age-col',
|
||||||
|
key: 'age',
|
||||||
|
sorter: (a, b) => a.age - b.age
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => <span id="chinese-title">Chinese Score</span>,
|
||||||
|
key: 'chinese',
|
||||||
|
defaultSortOrder: false,
|
||||||
|
className: 'chinese-col',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.chinese - b.chinese,
|
||||||
|
multiple: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => <span id="math-title">Math Score</span>,
|
||||||
|
defaultSortOrder: false,
|
||||||
|
className: 'math-col',
|
||||||
|
key: 'math',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.math - b.math,
|
||||||
|
multiple: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: () => <span id="english-title">English Score</span>,
|
||||||
|
className: 'english-col',
|
||||||
|
defaultSortOrder: false,
|
||||||
|
key: 'english',
|
||||||
|
sorter: {
|
||||||
|
compare: (a, b) => a.english - b.english,
|
||||||
|
multiple: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Address',
|
||||||
|
key: 'address',
|
||||||
|
filterOptions: [
|
||||||
|
{
|
||||||
|
label: 'London',
|
||||||
|
value: 'London'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'New York',
|
||||||
|
value: 'New York'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sidney',
|
||||||
|
value: 'Sidney'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
filter (value: any, row) {
|
||||||
|
return row.address.includes(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
name: 'John Brown',
|
||||||
|
age: 32,
|
||||||
|
address: 'New York No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 60,
|
||||||
|
english: 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jim Green',
|
||||||
|
age: 42,
|
||||||
|
address: 'London No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Joe Black',
|
||||||
|
age: 32,
|
||||||
|
address: 'Sidney No. 1 Lake Park',
|
||||||
|
chinese: 98,
|
||||||
|
math: 66,
|
||||||
|
english: 89
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jim Red',
|
||||||
|
age: 32,
|
||||||
|
address: 'London No. 2 Lake Park',
|
||||||
|
chinese: 88,
|
||||||
|
math: 99,
|
||||||
|
english: 89
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const tableRef = ref<DataTableInst | null>(null)
|
||||||
|
const wrapper = mount(
|
||||||
|
() => <NDataTable ref={tableRef} columns={columns} data={data} />,
|
||||||
|
{
|
||||||
|
attachTo: document.body
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const checkIsMatched = async (
|
||||||
|
colClassName: string,
|
||||||
|
target: number[]
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const cols = await wrapper.findAll(colClassName)
|
||||||
|
const colNums = cols.slice(1).map((item) => Number(item.text()))
|
||||||
|
const matchResult = String(colNums) === String(target)
|
||||||
|
if (!matchResult) {
|
||||||
|
console.log(colClassName, String(colNums), String(target))
|
||||||
|
}
|
||||||
|
return String(colNums) === String(target)
|
||||||
|
}
|
||||||
|
const checkScoreIsMatched = async (
|
||||||
|
targets: [number[], number[], number[]]
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const matchResult =
|
||||||
|
(await checkIsMatched('.chinese-col', targets[0])) &&
|
||||||
|
(await checkIsMatched('.math-col', targets[1])) &&
|
||||||
|
(await checkIsMatched('.english-col', targets[2]))
|
||||||
|
|
||||||
|
return matchResult
|
||||||
|
}
|
||||||
|
const chineseDom: HTMLElement | null =
|
||||||
|
document.querySelector('#chinese-title')
|
||||||
|
const mathDom: HTMLElement | null = document.querySelector('#math-title')
|
||||||
|
const englishDom: HTMLElement | null =
|
||||||
|
document.querySelector('#english-title')
|
||||||
|
const ageDom: HTMLElement | null = document.querySelector('#age-title')
|
||||||
|
|
||||||
|
it('chinese: descend, math: false, english: false', async () => {
|
||||||
|
await chineseDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[60, 66, 66, 99],
|
||||||
|
[70, 89, 89, 89]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
it('chinese: descend, math: descend, english: false', async () => {
|
||||||
|
await mathDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[66, 66, 60, 99],
|
||||||
|
[89, 89, 70, 89]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: descend, math: descend, english: descend', async () => {
|
||||||
|
await englishDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[66, 66, 60, 99],
|
||||||
|
[89, 89, 70, 89]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: ascend, math: descend, english: descend', async () => {
|
||||||
|
await chineseDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[88, 98, 98, 98],
|
||||||
|
[99, 66, 66, 60],
|
||||||
|
[89, 89, 89, 70]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: false, math: descend, english: descend', async () => {
|
||||||
|
await chineseDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[88, 98, 98, 98],
|
||||||
|
[99, 66, 66, 60],
|
||||||
|
[89, 89, 89, 70]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: false, math: ascend, english: descend', async () => {
|
||||||
|
await mathDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[60, 66, 66, 99],
|
||||||
|
[70, 89, 89, 89]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: false, math: false, english: descend', async () => {
|
||||||
|
await mathDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 88, 98],
|
||||||
|
[66, 66, 99, 60],
|
||||||
|
[89, 89, 89, 70]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('chinese: descend, math: false, english: descend', async () => {
|
||||||
|
await chineseDom?.click()
|
||||||
|
expect(
|
||||||
|
await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[66, 66, 60, 99],
|
||||||
|
[89, 89, 70, 89]
|
||||||
|
])
|
||||||
|
).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filter: Sidney and New York', async () => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.filter({
|
||||||
|
address: ['Sidney', 'New York']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await nextTick()
|
||||||
|
const result = await checkScoreIsMatched([
|
||||||
|
[98, 98],
|
||||||
|
[66, 60],
|
||||||
|
[89, 70]
|
||||||
|
])
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.filter(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('age: descend', async () => {
|
||||||
|
await ageDom?.click()
|
||||||
|
const result =
|
||||||
|
(await checkIsMatched('.age-col', [42, 32, 32, 32])) &&
|
||||||
|
(await checkScoreIsMatched([
|
||||||
|
[98, 98, 98, 88],
|
||||||
|
[66, 60, 66, 99],
|
||||||
|
[89, 70, 89, 89]
|
||||||
|
]))
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should work with `indent` prop', async () => {
|
it('should work with `indent` prop', async () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user