From 1b9b856d4c206aa8ce5d91184a02a57624b9ac2b Mon Sep 17 00:00:00 2001 From: wangyupei <2311595895@qq.com> Date: Thu, 7 Apr 2022 11:01:02 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(Table):=20Table=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/src/components/body-td/body-td.tsx | 2 +- .../src/components/column/column-types.ts | 17 +++- .../table/src/components/column/use-column.ts | 16 +++- .../src/components/header-th/header-th.tsx | 14 +-- .../src/components/header-th/use-header-th.ts | 28 +++--- .../table/src/components/header/header.scss | 5 + .../devui/table/src/components/sort/index.ts | 1 - .../table/src/components/sort/sort-types.ts | 11 +++ .../devui/table/src/components/sort/sort.tsx | 65 +++++-------- .../{composable => composables}/use-table.ts | 0 .../devui-vue/devui/table/src/store/index.ts | 12 +-- .../devui/table/src/store/store-types.ts | 6 +- .../devui-vue/devui/table/src/table-types.ts | 2 - packages/devui-vue/devui/table/src/table.tsx | 2 +- .../devui-vue/docs/components/table/index.md | 91 ++++++++++++++++--- 15 files changed, 172 insertions(+), 100 deletions(-) delete mode 100644 packages/devui-vue/devui/table/src/components/sort/index.ts create mode 100644 packages/devui-vue/devui/table/src/components/sort/sort-types.ts rename packages/devui-vue/devui/table/src/{composable => composables}/use-table.ts (100%) diff --git a/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx b/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx index 63afeb21c5..4ea65f1313 100644 --- a/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx +++ b/packages/devui-vue/devui/table/src/components/body-td/body-td.tsx @@ -2,7 +2,7 @@ import { defineComponent, toRef, inject } from 'vue'; import type { PropType } from 'vue'; import { Column } from '../column/column-types'; import { TABLE_TOKEN } from '../../table-types'; -import { useFixedColumn } from '../../composable/use-table'; +import { useFixedColumn } from '../../composables/use-table'; export default defineComponent({ name: 'DTableBodyTd', diff --git a/packages/devui-vue/devui/table/src/components/column/column-types.ts b/packages/devui-vue/devui/table/src/components/column/column-types.ts index b86a552d6f..db9d9c6260 100644 --- a/packages/devui-vue/devui/table/src/components/column/column-types.ts +++ b/packages/devui-vue/devui/table/src/components/column/column-types.ts @@ -5,10 +5,12 @@ import { TableStore } from '../../store/store-types'; // eslint-disable-next-line no-use-before-define export type Formatter = (row: DefaultRow, column: Column, cellValue: any, rowIndex: number) => VNode; -export type CompareFn = (field: string, a: T, b: T) => boolean; +export type SortMethod = (field: string, a: T, b: T) => boolean; export type ColumnType = 'checkable' | 'index' | ''; +export type SortDirection = 'ASC' | 'DESC' | ''; + export interface FilterConfig { id: number | string; name: string; @@ -47,9 +49,13 @@ export const tableColumnProps = { type: Boolean, default: false, }, - compareFn: { - type: Function as PropType, - default: (field: string, a: any, b: any): boolean => a[field] < b[field], + sortDirection: { + type: String as PropType, + default: '', + }, + sortMethod: { + type: Function as PropType, + default: (field: string, a: any, b: any): boolean => a[field] > b[field], }, filterable: { type: Boolean, @@ -92,6 +98,7 @@ export interface Column { header?: string; order?: number; sortable?: boolean; + sortDirection: SortDirection; filterable?: boolean; filterMultiple?: boolean; filterList?: FilterConfig[]; @@ -100,7 +107,7 @@ export interface Column { renderHeader?: (column: Column, store: TableStore) => VNode; renderCell?: (rowData: DefaultRow, columnItem: Column, store: TableStore, rowIndex: number) => VNode; formatter?: Formatter; - compareFn?: CompareFn; + sortMethod: SortMethod; customFilterTemplate?: CustomFilterSlot; subColumns?: Slot; } diff --git a/packages/devui-vue/devui/table/src/components/column/use-column.ts b/packages/devui-vue/devui/table/src/components/column/use-column.ts index 49b209ed0d..44c7d5044e 100644 --- a/packages/devui-vue/devui/table/src/components/column/use-column.ts +++ b/packages/devui-vue/devui/table/src/components/column/use-column.ts @@ -12,10 +12,11 @@ export function createColumn(props: ToRefs, slots: Slots): Col field, header, sortable, + sortDirection, width, minWidth, formatter, - compareFn, + sortMethod, filterable, filterList, filterMultiple, @@ -51,10 +52,15 @@ export function createColumn(props: ToRefs, slots: Slots): Col ); // 排序功能 - watch([sortable, compareFn], ([sortableVal, compareFnVal]) => { - column.sortable = sortableVal; - column.compareFn = compareFnVal; - }); + watch( + [sortable, sortDirection, sortMethod], + ([sortableVal, sortDirectionVal, sortMethodVal]) => { + column.sortable = sortableVal; + column.sortDirection = sortDirectionVal; + column.sortMethod = sortMethodVal; + }, + { immediate: true } + ); // 过滤功能 watch( diff --git a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx index bda79e50b3..5dfef5440a 100644 --- a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx +++ b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx @@ -2,9 +2,9 @@ import { defineComponent, inject, toRefs } from 'vue'; import type { PropType } from 'vue'; import { Column } from '../column/column-types'; import { TABLE_TOKEN } from '../../table-types'; -import { Sort } from '../sort'; +import Sort from '../sort/sort'; import { Filter } from '../filter'; -import { useFixedColumn } from '../../composable/use-table'; +import { useFixedColumn } from '../../composables/use-table'; import { useSort, useFilter } from './use-header-th'; export default defineComponent({ @@ -18,19 +18,19 @@ export default defineComponent({ setup(props: { column: Column }) { const table = inject(TABLE_TOKEN); const { column } = toRefs(props); - const directionRef = useSort(table.store, column); + const { direction, sortClass } = useSort(table.store, column); const filteredRef = useFilter(table.store, column); const { stickyClass, stickyStyle } = useFixedColumn(column); return () => ( - +
- {props.column.renderHeader?.(column.value, table.store)} - {props.column.filterable && ( + {column.value.renderHeader?.(column.value, table.store)} + {column.value.filterable && ( )} + {column.value.sortable && }
- {props.column.sortable && } ); }, diff --git a/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts b/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts index ba631f20d2..33302ba414 100644 --- a/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts +++ b/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts @@ -1,22 +1,28 @@ -import { ref, watch, Ref, shallowRef } from 'vue'; -import { Column, FilterResults } from '../column/column-types'; +import { ref, watch, Ref, shallowRef, computed } from 'vue'; +import type { ComputedRef } from 'vue'; +import { Column, FilterResults, SortDirection } from '../column/column-types'; import { TableStore } from '../../store/store-types'; -import { SortDirection } from '../../table-types'; -export const useSort = (store: TableStore, column: Ref): Ref => { - // 排序功能 - const directionRef = ref('DESC'); +export const useSort = ( + store: TableStore, + column: Ref +): { direction: Ref; sortClass: ComputedRef> } => { + const direction = ref(column.value.sortDirection); + const sortClass = computed(() => ({ + 'sort-active': Boolean(direction.value), + })); + watch( - [directionRef, column], - ([direction, column]) => { - if (column.sortable) { - store.sortData(column.field, direction, column.compareFn); + [direction, column], + ([directionVal, columnVal]) => { + if (columnVal.sortable && columnVal.field) { + store.sortData(columnVal.field, directionVal, columnVal.sortMethod); } }, { immediate: true } ); - return directionRef; + return { direction, sortClass }; }; export const useFilter = (store: TableStore, column: Ref): Ref => { diff --git a/packages/devui-vue/devui/table/src/components/header/header.scss b/packages/devui-vue/devui/table/src/components/header/header.scss index 19647daa0f..c01883c9e6 100644 --- a/packages/devui-vue/devui/table/src/components/header/header.scss +++ b/packages/devui-vue/devui/table/src/components/header/header.scss @@ -16,6 +16,11 @@ border: none; border-bottom: 1px solid $devui-line; } + + .sort-active { + background-color: $devui-list-item-hover-bg; + border-radius: $devui-border-radius 0 0 $devui-border-radius; + } } .header-container { diff --git a/packages/devui-vue/devui/table/src/components/sort/index.ts b/packages/devui-vue/devui/table/src/components/sort/index.ts deleted file mode 100644 index 3577964f8a..0000000000 --- a/packages/devui-vue/devui/table/src/components/sort/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Sort } from './sort'; diff --git a/packages/devui-vue/devui/table/src/components/sort/sort-types.ts b/packages/devui-vue/devui/table/src/components/sort/sort-types.ts new file mode 100644 index 0000000000..60e46ad299 --- /dev/null +++ b/packages/devui-vue/devui/table/src/components/sort/sort-types.ts @@ -0,0 +1,11 @@ +import type { ExtractPropTypes, PropType } from 'vue'; +import { SortDirection } from '../column/column-types'; + +export const sortProps = { + modelValue: { + type: String as PropType, + default: '', + }, +}; + +export type SortProps = ExtractPropTypes; diff --git a/packages/devui-vue/devui/table/src/components/sort/sort.tsx b/packages/devui-vue/devui/table/src/components/sort/sort.tsx index a1b7862ba0..c387925b80 100644 --- a/packages/devui-vue/devui/table/src/components/sort/sort.tsx +++ b/packages/devui-vue/devui/table/src/components/sort/sort.tsx @@ -1,33 +1,22 @@ -import { defineComponent, PropType } from 'vue'; -import { SortDirection } from '../../table-types'; +import { defineComponent } from 'vue'; +import { sortProps, SortProps } from './sort-types'; import './sort.scss'; -export const Sort = defineComponent({ - props: { - modelValue: { - type: String as PropType, - default: '', - }, - 'onUpdate:modelValue': { - type: Function as PropType<(v: SortDirection) => void>, - }, - }, +export default defineComponent({ + props: sortProps, emits: ['update:modelValue'], - setup(props, ctx) { + setup(props: SortProps, ctx) { + const directionMap = { + ASC: 'DESC', + DESC: '', + default: 'ASC', + }; const changeDirection = () => { - let direction = ''; - if (props.modelValue === 'ASC') { - direction = 'DESC'; - } else if (props.modelValue === 'DESC') { - direction = ''; - } else { - direction = 'ASC'; - } - ctx.emit('update:modelValue', direction); + ctx.emit('update:modelValue', directionMap[props.modelValue || 'default']); }; return () => ( - + - + - - - - + + + + + values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.085309222 0" + type="matrix" + in="shadowBlurOuter1"> - - - - + + + + diff --git a/packages/devui-vue/devui/table/src/composable/use-table.ts b/packages/devui-vue/devui/table/src/composables/use-table.ts similarity index 100% rename from packages/devui-vue/devui/table/src/composable/use-table.ts rename to packages/devui-vue/devui/table/src/composables/use-table.ts diff --git a/packages/devui-vue/devui/table/src/store/index.ts b/packages/devui-vue/devui/table/src/store/index.ts index 94e5dd5c50..521f01c5bd 100644 --- a/packages/devui-vue/devui/table/src/store/index.ts +++ b/packages/devui-vue/devui/table/src/store/index.ts @@ -1,5 +1,5 @@ import { watch, Ref, ref, computed, unref } from 'vue'; -import { Column, CompareFn, FilterResults } from '../components/column/column-types'; +import { Column, SortMethod, FilterResults } from '../components/column/column-types'; import { SortDirection } from '../table-types'; import { TableStore } from './store-types'; @@ -121,15 +121,11 @@ const createSelection = (dataSource: Ref, _data: Ref) => { }; const createSorter = (dataSource: Ref, _data: Ref) => { - const sortData = ( - field: string, - direction: SortDirection, - compareFn: CompareFn = (fieldKey: string, a: T, b: T) => a[fieldKey] > b[fieldKey] - ) => { + const sortData = (field: string, direction: SortDirection, sortMethod: SortMethod) => { if (direction === 'ASC') { - _data.value = _data.value.sort((a, b) => (compareFn(field, a, b) ? 1 : -1)); + _data.value = _data.value.sort((a, b) => (sortMethod(field, a, b) ? 1 : -1)); } else if (direction === 'DESC') { - _data.value = _data.value.sort((a, b) => (!compareFn(field, a, b) ? 1 : -1)); + _data.value = _data.value.sort((a, b) => (sortMethod(field, a, b) ? -1 : 1)); } else { _data.value = [...dataSource.value]; } diff --git a/packages/devui-vue/devui/table/src/store/store-types.ts b/packages/devui-vue/devui/table/src/store/store-types.ts index f8b426849e..7d74995b6e 100644 --- a/packages/devui-vue/devui/table/src/store/store-types.ts +++ b/packages/devui-vue/devui/table/src/store/store-types.ts @@ -1,6 +1,6 @@ import type { Ref } from 'vue'; -import { SortDirection } from '../table-types'; -import { Column, CompareFn, FilterResults } from '../components/column/column-types'; +import { SortDirection } from '../components/sort/sort-types'; +import { Column, SortMethod, FilterResults } from '../components/column/column-types'; export interface TableStore> { states: { @@ -17,7 +17,7 @@ export interface TableStore> { removeColumn(column: Column): void; updateColumns(): void; getCheckedRows(): T[]; - sortData(field: string, direction: SortDirection, compareFn: CompareFn): void; + sortData(field: string, direction: SortDirection, sortMethod: SortMethod): void; filterData(field: string, results: FilterResults): void; resetFilterData(): void; } diff --git a/packages/devui-vue/devui/table/src/table-types.ts b/packages/devui-vue/devui/table/src/table-types.ts index 84347d85cd..b810a1531d 100644 --- a/packages/devui-vue/devui/table/src/table-types.ts +++ b/packages/devui-vue/devui/table/src/table-types.ts @@ -110,5 +110,3 @@ export interface TableMethods> { } export const TABLE_TOKEN: InjectionKey = Symbol(); - -export type SortDirection = 'ASC' | 'DESC' | ''; diff --git a/packages/devui-vue/devui/table/src/table.tsx b/packages/devui-vue/devui/table/src/table.tsx index 3b4fb586ff..2cd2ac22f1 100644 --- a/packages/devui-vue/devui/table/src/table.tsx +++ b/packages/devui-vue/devui/table/src/table.tsx @@ -1,6 +1,6 @@ import { provide, defineComponent, getCurrentInstance, computed, toRef, ref, onMounted, nextTick } from 'vue'; import { Table, TableProps, TablePropsTypes, TABLE_TOKEN, DefaultRow } from './table-types'; -import { useTable } from './composable/use-table'; +import { useTable } from './composables/use-table'; import { createStore } from './store'; import FixHeader from './components/fix-header'; import NormalHeader from './components/normal-header'; diff --git a/packages/devui-vue/docs/components/table/index.md b/packages/devui-vue/docs/components/table/index.md index 9078d56010..44dab679c5 100644 --- a/packages/devui-vue/docs/components/table/index.md +++ b/packages/devui-vue/docs/components/table/index.md @@ -807,6 +807,60 @@ export default defineComponent({ ::: +### 列排序 + +:::demo `sortable`参数设置为`true`可以支持列排序。 + +```vue + + + +``` + +::: + ### d-table 参数 | 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo | @@ -834,18 +888,19 @@ export default defineComponent({ ### d-column 参数 -| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo | -| :--------- | :----------------- | :----------------------------------- | :------------------------------------------ | :-------------------- | -| header | `string` | -- | 可选,对应列的标题 | [基本用法](#基本用法) | -| field | `string` | -- | 可选,对应列内容的字段名 | [基本用法](#基本用法) | -| type | `ColumnType` | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | -| width | `string \| number` | -- | 可选,对应列的宽度,单位`px` | -| min-width | `string \| number` | -- | 可选,对应列的最小宽度,单位`px` | -| sortable | `boolean` | false | 可选,对行数据按照该列的顺序进行排序 | -| fixedLeft | `string` | -- | 可选,该列固定到左侧的距离,如:'100px' | [固定列](#固定列) | -| fixedRight | `string` | -- | 可选,该列固定到右侧的距离,如:'100px' | [固定列](#固定列) | -| formatter | `Formatter` | -- | 可选,格式化列内容 | -| compareFn | `CompareFn` | (field, a, b) => a[field] > b[field] | 可选,用于排序的比较函数 | +| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo | +| :------------- | :----------------- | :----------------------------------- | :------------------------------------------ | :-------------------- | +| header | `string` | -- | 可选,对应列的标题 | [基本用法](#基本用法) | +| field | `string` | -- | 可选,对应列内容的字段名 | [基本用法](#基本用法) | +| type | `ColumnType` | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | +| width | `string \| number` | -- | 可选,对应列的宽度,单位`px` | +| min-width | `string \| number` | -- | 可选,对应列的最小宽度,单位`px` | +| fixedLeft | `string` | -- | 可选,该列固定到左侧的距离,如:'100px' | [固定列](#固定列) | +| fixedRight | `string` | -- | 可选,该列固定到右侧的距离,如:'100px' | [固定列](#固定列) | +| formatter | `Formatter` | -- | 可选,格式化列内容 | +| sortable | `boolean` | false | 可选,对行数据按照该列的顺序进行排序 | [列排序](#列排序) | +| sort-direction | `SortDirection` | '' | 可选,设置该列的排序状态 | [列排序](#列排序) | +| sort-method | `SortMethod` | (field, a, b) => a[field] > b[field] | 可选,用于排序的比较函数 | ### d-column 插槽 @@ -882,7 +937,7 @@ type BorderType = '' | 'bordered' | 'borderless'; #### ColumnType ```ts -type ColumnType = 'checkable' | ''; +type ColumnType = 'checkable' | 'index' | ''; ``` #### Formatter @@ -891,8 +946,14 @@ type ColumnType = 'checkable' | ''; type Formatter = (row: any, column: any, cellValue: any, rowIndex: number) => VNode; ``` -#### CompareFn +#### SortDirection + +```ts +type SortDirection = 'ASC' | 'DESC' | ''; +``` + +#### SortMethod ```ts -type CompareFn = (field: string, a: T, b: T) => boolean; +type SortMethod = (field: string, a: T, b: T) => boolean; ``` From 6f411e2bc51683afe866ff1a3962e676c5e82201 Mon Sep 17 00:00:00 2001 From: wangyupei <2311595895@qq.com> Date: Mon, 11 Apr 2022 19:49:30 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(Table):=20=E5=AE=8C=E6=88=90Table?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devui/table/__tests__/table.spec.tsx | 58 ++++++++++++++++--- .../src/components/column/column-types.ts | 3 +- .../src/components/header-th/header-th.tsx | 13 +++-- .../src/components/header-th/use-header-th.ts | 50 +++++++++++----- .../table/src/components/sort/sort-types.ts | 2 +- .../devui/table/src/components/sort/sort.tsx | 10 ++-- .../devui-vue/devui/table/src/store/index.ts | 18 +++--- .../devui/table/src/store/store-types.ts | 6 +- packages/devui-vue/devui/table/src/table.tsx | 1 + .../devui-vue/docs/components/table/index.md | 58 ++++++++++++------- 10 files changed, 150 insertions(+), 69 deletions(-) diff --git a/packages/devui-vue/devui/table/__tests__/table.spec.tsx b/packages/devui-vue/devui/table/__tests__/table.spec.tsx index a3b507782e..ec50c8e7b9 100644 --- a/packages/devui-vue/devui/table/__tests__/table.spec.tsx +++ b/packages/devui-vue/devui/table/__tests__/table.spec.tsx @@ -8,27 +8,27 @@ let data: Array> = []; describe('d-table', () => { beforeEach(() => { data = [ - { - firstName: 'Mark', - lastName: 'Otto', - date: '1990/01/11', - gender: 'Male', - }, { firstName: 'Jacob', lastName: 'Thornton', gender: 'Female', date: '1990/01/12', }, + { + firstName: 'Mark', + lastName: 'Otto', + date: '1990/01/11', + gender: 'Male', + }, { firstName: 'Danni', lastName: 'Chen', - gender: 'Male', + gender: 'Female', date: '1990/01/13', }, { - firstName: 'green', - lastName: 'gerong', + firstName: 'Green', + lastName: 'Gerong', gender: 'Male', date: '1990/01/14', }, @@ -232,4 +232,44 @@ describe('d-table', () => { expect(tableHeader.findAll('tr')[0].findAll('th')[1].attributes('rowspan')).toBe('2'); wrapper.unmount(); }); + + it('sort', async () => { + const handleSortChange = jest.fn(); + const wrapper = mount({ + setup() { + const sortDateMethod = (a, b) => { + return a.date > b.date; + }; + return () => ( + + + + + + + ); + }, + }); + + await nextTick(); + await nextTick(); + + const table = wrapper.find('.devui-table'); + const tableHeader = table.find('.devui-table__thead'); + const lastTh = tableHeader.find('tr').findAll('th')[3]; + expect(lastTh.classes()).toContain('sort-active'); + + const tableBody = table.find('.devui-table__tbody'); + const lastTd = tableBody.find('tr').findAll('td')[3]; + expect(lastTd.text()).toBe('1990/01/11'); + + const sortIcon = lastTh.find('.sort-clickable'); + await sortIcon.trigger('click'); + expect(lastTd.text()).toBe('1990/01/14'); + expect(handleSortChange).toBeCalled(); + + await sortIcon.trigger('click'); + expect(lastTd.text()).toBe('1990/01/12'); + expect(handleSortChange).toBeCalled(); + }); }); diff --git a/packages/devui-vue/devui/table/src/components/column/column-types.ts b/packages/devui-vue/devui/table/src/components/column/column-types.ts index db9d9c6260..4b2cf93fb3 100644 --- a/packages/devui-vue/devui/table/src/components/column/column-types.ts +++ b/packages/devui-vue/devui/table/src/components/column/column-types.ts @@ -5,7 +5,7 @@ import { TableStore } from '../../store/store-types'; // eslint-disable-next-line no-use-before-define export type Formatter = (row: DefaultRow, column: Column, cellValue: any, rowIndex: number) => VNode; -export type SortMethod = (field: string, a: T, b: T) => boolean; +export type SortMethod = (a: T, b: T) => boolean; export type ColumnType = 'checkable' | 'index' | ''; @@ -55,7 +55,6 @@ export const tableColumnProps = { }, sortMethod: { type: Function as PropType, - default: (field: string, a: any, b: any): boolean => a[field] > b[field], }, filterable: { type: Boolean, diff --git a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx index 5dfef5440a..3cc945e180 100644 --- a/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx +++ b/packages/devui-vue/devui/table/src/components/header-th/header-th.tsx @@ -15,21 +15,24 @@ export default defineComponent({ required: true, }, }, - setup(props: { column: Column }) { + setup(props: { column: Column }, { expose }) { const table = inject(TABLE_TOKEN); + const store = table.store; const { column } = toRefs(props); - const { direction, sortClass } = useSort(table.store, column); - const filteredRef = useFilter(table.store, column); + const { direction, sortClass, handleSort, clearSortOrder } = useSort(column); + const filteredRef = useFilter(store, column); const { stickyClass, stickyStyle } = useFixedColumn(column); + expose({ clearSortOrder }); + return () => ( ); diff --git a/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts b/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts index 33302ba414..7ccf17e7ef 100644 --- a/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts +++ b/packages/devui-vue/devui/table/src/components/header-th/use-header-th.ts @@ -1,28 +1,48 @@ -import { ref, watch, Ref, shallowRef, computed } from 'vue'; +import { ref, watch, Ref, shallowRef, computed, getCurrentInstance, inject, onMounted } from 'vue'; import type { ComputedRef } from 'vue'; import { Column, FilterResults, SortDirection } from '../column/column-types'; import { TableStore } from '../../store/store-types'; +import { TABLE_TOKEN } from '../../table-types'; -export const useSort = ( - store: TableStore, - column: Ref -): { direction: Ref; sortClass: ComputedRef> } => { +interface UseSort { + direction: Ref; + sortClass: ComputedRef>; + handleSort: (val: SortDirection) => void; + clearSortOrder: () => void; +} + +export const useSort = (column: Ref): UseSort => { + const table = inject(TABLE_TOKEN); + const store = table.store; const direction = ref(column.value.sortDirection); const sortClass = computed(() => ({ 'sort-active': Boolean(direction.value), })); - - watch( - [direction, column], - ([directionVal, columnVal]) => { - if (columnVal.sortable && columnVal.field) { - store.sortData(columnVal.field, directionVal, columnVal.sortMethod); + const thInstance = getCurrentInstance(); + thInstance && store.states.thList.push(thInstance); + onMounted(() => { + column.value.sortable && column.value.sortDirection && store.sortData?.(direction.value, column.value.sortMethod); + }); + const execClearSortOrder = () => { + store.states.thList.forEach((th) => { + if (th !== thInstance) { + th.exposed?.clearSortOrder?.(); } - }, - { immediate: true } - ); + }); + }; + + const handleSort = (val: SortDirection) => { + direction.value = val; + execClearSortOrder(); + store.sortData?.(direction.value, column.value.sortMethod); + table.emit('sort-change', { field: column.value.field, direction: direction.value }); + }; + + const clearSortOrder = () => { + direction.value = ''; + }; - return { direction, sortClass }; + return { direction, sortClass, handleSort, clearSortOrder }; }; export const useFilter = (store: TableStore, column: Ref): Ref => { diff --git a/packages/devui-vue/devui/table/src/components/sort/sort-types.ts b/packages/devui-vue/devui/table/src/components/sort/sort-types.ts index 60e46ad299..521d0e8ba8 100644 --- a/packages/devui-vue/devui/table/src/components/sort/sort-types.ts +++ b/packages/devui-vue/devui/table/src/components/sort/sort-types.ts @@ -2,7 +2,7 @@ import type { ExtractPropTypes, PropType } from 'vue'; import { SortDirection } from '../column/column-types'; export const sortProps = { - modelValue: { + sortDirection: { type: String as PropType, default: '', }, diff --git a/packages/devui-vue/devui/table/src/components/sort/sort.tsx b/packages/devui-vue/devui/table/src/components/sort/sort.tsx index c387925b80..cfb25a75fb 100644 --- a/packages/devui-vue/devui/table/src/components/sort/sort.tsx +++ b/packages/devui-vue/devui/table/src/components/sort/sort.tsx @@ -4,7 +4,7 @@ import './sort.scss'; export default defineComponent({ props: sortProps, - emits: ['update:modelValue'], + emits: ['sort'], setup(props: SortProps, ctx) { const directionMap = { ASC: 'DESC', @@ -12,7 +12,7 @@ export default defineComponent({ default: 'ASC', }; const changeDirection = () => { - ctx.emit('update:modelValue', directionMap[props.modelValue || 'default']); + ctx.emit('sort', directionMap[props.sortDirection || 'default']); }; return () => ( @@ -21,9 +21,9 @@ export default defineComponent({ class={[ 'datatable-svg', { - 'sort-icon-default': !props.modelValue, - 'sort-icon-asc': props.modelValue === 'ASC', - 'sort-icon-desc': props.modelValue === 'DESC', + 'sort-icon-default': !props.sortDirection, + 'sort-icon-asc': props.sortDirection === 'ASC', + 'sort-icon-desc': props.sortDirection === 'DESC', }, ]}> diff --git a/packages/devui-vue/devui/table/src/store/index.ts b/packages/devui-vue/devui/table/src/store/index.ts index 521f01c5bd..c3a6c0891e 100644 --- a/packages/devui-vue/devui/table/src/store/index.ts +++ b/packages/devui-vue/devui/table/src/store/index.ts @@ -1,6 +1,5 @@ -import { watch, Ref, ref, computed, unref } from 'vue'; -import { Column, SortMethod, FilterResults } from '../components/column/column-types'; -import { SortDirection } from '../table-types'; +import { watch, Ref, ref, computed, unref, ComponentInternalInstance } from 'vue'; +import { Column, SortMethod, FilterResults, SortDirection } from '../components/column/column-types'; import { TableStore } from './store-types'; function replaceColumn(array: any, column: any) { @@ -121,16 +120,18 @@ const createSelection = (dataSource: Ref, _data: Ref) => { }; const createSorter = (dataSource: Ref, _data: Ref) => { - const sortData = (field: string, direction: SortDirection, sortMethod: SortMethod) => { + const sortData = (direction: SortDirection, sortMethod: SortMethod) => { if (direction === 'ASC') { - _data.value = _data.value.sort((a, b) => (sortMethod(field, a, b) ? 1 : -1)); + _data.value = _data.value.sort((a, b) => (sortMethod ? (sortMethod(a, b) ? 1 : -1) : 0)); } else if (direction === 'DESC') { - _data.value = _data.value.sort((a, b) => (sortMethod(field, a, b) ? -1 : 1)); + _data.value = _data.value.sort((a, b) => (sortMethod ? (sortMethod(a, b) ? -1 : 1) : 0)); } else { _data.value = [...dataSource.value]; } }; - return { sortData }; + + const thList: ComponentInternalInstance[] = []; + return { sortData, thList }; }; const createFilter = (dataSource: Ref, _data: Ref) => { @@ -172,7 +173,7 @@ export function createStore(dataSource: Ref): TableStore { const { _columns, flatColumns, insertColumn, removeColumn, sortColumn, updateColumns } = createColumnGenerator(); const { _checkAll, _checkList, _halfChecked, getCheckedRows } = createSelection(dataSource, _data); - const { sortData } = createSorter(dataSource, _data); + const { sortData, thList } = createSorter(dataSource, _data); const { filterData, resetFilterData } = createFilter(dataSource, _data); const { isFixedLeft } = createFixedLogic(_columns); @@ -186,6 +187,7 @@ export function createStore(dataSource: Ref): TableStore { _checkAll, _halfChecked, isFixedLeft, + thList, }, insertColumn, sortColumn, diff --git a/packages/devui-vue/devui/table/src/store/store-types.ts b/packages/devui-vue/devui/table/src/store/store-types.ts index 7d74995b6e..7f4b979902 100644 --- a/packages/devui-vue/devui/table/src/store/store-types.ts +++ b/packages/devui-vue/devui/table/src/store/store-types.ts @@ -1,6 +1,5 @@ -import type { Ref } from 'vue'; -import { SortDirection } from '../components/sort/sort-types'; -import { Column, SortMethod, FilterResults } from '../components/column/column-types'; +import type { ComponentInternalInstance, Ref } from 'vue'; +import { Column, SortMethod, FilterResults, SortDirection } from '../components/column/column-types'; export interface TableStore> { states: { @@ -11,6 +10,7 @@ export interface TableStore> { _checkAll: Ref; _halfChecked: Ref; isFixedLeft: Ref; + thList: ComponentInternalInstance[]; }; insertColumn(column: Column, parent: any): void; sortColumn(): void; diff --git a/packages/devui-vue/devui/table/src/table.tsx b/packages/devui-vue/devui/table/src/table.tsx index 2cd2ac22f1..95af954e85 100644 --- a/packages/devui-vue/devui/table/src/table.tsx +++ b/packages/devui-vue/devui/table/src/table.tsx @@ -16,6 +16,7 @@ export default defineComponent({ dLoading: Loading, }, props: TableProps, + emits: ['sort-change'], setup(props: TablePropsTypes, ctx) { const table = getCurrentInstance() as Table; const store = createStore(toRef(props, 'data')); diff --git a/packages/devui-vue/docs/components/table/index.md b/packages/devui-vue/docs/components/table/index.md index c38aa3f4cc..2c66c3400f 100644 --- a/packages/devui-vue/docs/components/table/index.md +++ b/packages/devui-vue/docs/components/table/index.md @@ -809,15 +809,15 @@ export default defineComponent({ ### 列排序 -:::demo `sortable`参数设置为`true`可以支持列排序。 +:::demo `sortable`参数设置为`true`可以支持列排序;`sort-direction`设置初始化时的排序方式;`sort-method`用来定义每一列的排序方法;`sort-change`是排序的回调事件,返回该列的排序信息:`field`排序字段和`direction`排序方向。 ```vue @@ -852,8 +852,18 @@ export default defineComponent({ date: '1990/01/14', }, ]); + const handleSortChange = ({ field, direction }) => { + console.log('field', field); + console.log('direction', direction); + }; + const sortDateMethod = (a, b) => { + return a.date > b.date; + }; + const sortNameMethod = (a, b) => { + return a.lastName > b.lastName; + }; - return { dataSource }; + return { dataSource, handleSortChange, sortDateMethod, sortNameMethod }; }, }); @@ -880,6 +890,12 @@ export default defineComponent({ | span-method | [SpanMethod](#spanmethod) | -- | 可选,合并单元格的计算方法 | [合并单元格](#合并单元格) | | border-type | [BorderType](#bordertype) | '' | 可选,表格边框类型,默认有行边框;`bordered`: 全边框;`borderless`: 无边框 | [表格样式](#表格样式) | +### Table 事件 + +| 事件名 | 回调参数 | 说明 | 跳转 Demo | +| :---------- | :----------------------------------------------------------- | :----------------------------- | :---------------- | +| sort-change | `Function(obj: { field: string; direction: SortDirection })` | 排序回调事件,返回该列排序信息 | [列排序](#列排序) | + ### Table 方法 | 方法名 | 类型 | 说明 | @@ -888,19 +904,19 @@ export default defineComponent({ ### Column 参数 -| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo | -| :------------- | :----------------- | :----------------------------------- | :------------------------------------------ | :-------------------- | -| header | `string` | -- | 可选,对应列的标题 | [基本用法](#基本用法) | -| field | `string` | -- | 可选,对应列内容的字段名 | [基本用法](#基本用法) | -| type | `ColumnType` | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | -| width | `string \| number` | -- | 可选,对应列的宽度,单位`px` | -| min-width | `string \| number` | -- | 可选,对应列的最小宽度,单位`px` | -| fixedLeft | `string` | -- | 可选,该列固定到左侧的距离,如:'100px' | [固定列](#固定列) | -| fixedRight | `string` | -- | 可选,该列固定到右侧的距离,如:'100px' | [固定列](#固定列) | -| formatter | `Formatter` | -- | 可选,格式化列内容 | -| sortable | `boolean` | false | 可选,对行数据按照该列的顺序进行排序 | [列排序](#列排序) | -| sort-direction | `SortDirection` | '' | 可选,设置该列的排序状态 | [列排序](#列排序) | -| sort-method | `SortMethod` | (field, a, b) => a[field] > b[field] | 可选,用于排序的比较函数 | +| 参数名 | 类型 | 默认值 | 说明 | 跳转 Demo | +| :------------- | :------------------------------ | :----- | :------------------------------------------ | :-------------------- | +| header | `string` | -- | 可选,对应列的标题 | [基本用法](#基本用法) | +| field | `string` | -- | 可选,对应列内容的字段名 | [基本用法](#基本用法) | +| type | `ColumnType` | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | +| width | `string \| number` | -- | 可选,对应列的宽度,单位`px` | +| min-width | `string \| number` | -- | 可选,对应列的最小宽度,单位`px` | +| fixedLeft | `string` | -- | 可选,该列固定到左侧的距离,如:'100px' | [固定列](#固定列) | +| fixedRight | `string` | -- | 可选,该列固定到右侧的距离,如:'100px' | [固定列](#固定列) | +| formatter | `Formatter` | -- | 可选,格式化列内容 | +| sortable | `boolean` | false | 可选,对行数据按照该列的顺序进行排序 | [列排序](#列排序) | +| sort-direction | [SortDirection](#sortdirection) | '' | 可选,设置该列的排序状态 | [列排序](#列排序) | +| sort-method | [SortMethod](#sortmethod) | -- | 可选,用于排序的比较函数 | [列排序](#列排序) | ### Column 插槽 @@ -945,7 +961,7 @@ type SortDirection = 'ASC' | 'DESC' | ''; #### SortMethod ```ts -type SortMethod = (field: string, a: T, b: T) => boolean; +type SortMethod = (a: T, b: T) => boolean; ``` ### Column 类型定义 From c9e551f13d65698e40a1b3d269f63c26fd69e289 Mon Sep 17 00:00:00 2001 From: wangyupei <2311595895@qq.com> Date: Mon, 11 Apr 2022 19:58:05 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(Table):=20Table=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=B0=8F=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devui-vue/docs/components/table/index.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/devui-vue/docs/components/table/index.md b/packages/devui-vue/docs/components/table/index.md index 2c66c3400f..8a02f1996b 100644 --- a/packages/devui-vue/docs/components/table/index.md +++ b/packages/devui-vue/docs/components/table/index.md @@ -908,12 +908,12 @@ export default defineComponent({ | :------------- | :------------------------------ | :----- | :------------------------------------------ | :-------------------- | | header | `string` | -- | 可选,对应列的标题 | [基本用法](#基本用法) | | field | `string` | -- | 可选,对应列内容的字段名 | [基本用法](#基本用法) | -| type | `ColumnType` | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | +| type | [ColumnType](#columntype) | '' | 可选,列的类型,设置`checkable`会显示多选框 | [表格多选](#表格多选) | | width | `string \| number` | -- | 可选,对应列的宽度,单位`px` | | min-width | `string \| number` | -- | 可选,对应列的最小宽度,单位`px` | | fixedLeft | `string` | -- | 可选,该列固定到左侧的距离,如:'100px' | [固定列](#固定列) | | fixedRight | `string` | -- | 可选,该列固定到右侧的距离,如:'100px' | [固定列](#固定列) | -| formatter | `Formatter` | -- | 可选,格式化列内容 | +| formatter | [Formatter](#formatter) | -- | 可选,格式化列内容 | | sortable | `boolean` | false | 可选,对行数据按照该列的顺序进行排序 | [列排序](#列排序) | | sort-direction | [SortDirection](#sortdirection) | '' | 可选,设置该列的排序状态 | [列排序](#列排序) | | sort-method | [SortMethod](#sortmethod) | -- | 可选,用于排序的比较函数 | [列排序](#列排序) | @@ -952,18 +952,6 @@ type SpanMethod = (data: { type BorderType = '' | 'bordered' | 'borderless'; ``` -#### SortDirection - -```ts -type SortDirection = 'ASC' | 'DESC' | ''; -``` - -#### SortMethod - -```ts -type SortMethod = (a: T, b: T) => boolean; -``` - ### Column 类型定义
@@ -979,3 +967,15 @@ type ColumnType = 'checkable' | 'index' | ''; ```ts type Formatter = (row: any, column: any, cellValue: any, rowIndex: number) => VNode; ``` + +#### SortDirection + +```ts +type SortDirection = 'ASC' | 'DESC' | ''; +``` + +#### SortMethod + +```ts +type SortMethod = (a: T, b: T) => boolean; +```
- {column.value.renderHeader?.(column.value, table.store)} + {column.value.renderHeader?.(column.value, store)} {column.value.filterable && ( )} - {column.value.sortable && } + {column.value.sortable && }