feat: table add filterResetToDefaultFilteredValue & filterSearch funcion

pull/5820/head
tangjinzhou 2022-05-22 14:23:53 +08:00
parent a4b6c0aee4
commit 37d35f7801
11 changed files with 195 additions and 39 deletions

View File

@ -0,0 +1,113 @@
<docs>
---
order: 6.2
version: 4.19.0
title:
en-US: Filter search
zh-CN: 自定义筛选的搜索
---
## zh-CN
`filterSearch` 用于开启筛选项的搜索通过 `filterSearch:(input, record) => boolean` 设置自定义筛选方法
## en-US
`filterSearch` is used to enable search of filter items, and you can set a custom filter method through `filterSearch:(input, record) => boolean`.
</docs>
<template>
<a-table :columns="columns" :data-source="data" @change="onChange"></a-table>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import type { TableProps } from 'ant-design-vue';
export default defineComponent({
setup() {
const columns: TableProps['columns'] = [
{
title: 'Name',
dataIndex: 'name',
filters: [
{
text: 'Joe',
value: 'Joe',
},
{
text: 'Category 1',
value: 'Category 1',
},
{
text: 'Category 2',
value: 'Category 2',
},
],
filterMode: 'tree',
filterSearch: true,
onFilter: (value, record) => record.name.startsWith(value),
width: '30%',
},
{
title: 'Age',
dataIndex: 'age',
sorter: (a, b) => a.age - b.age,
},
{
title: 'Address',
dataIndex: 'address',
filters: [
{
text: 'London',
value: 'London',
},
{
text: 'New York',
value: 'New York',
},
],
onFilter: (value, record) => record.address.startsWith(value),
filterSearch: (input, filter) => (filter.value as string).indexOf(input) > -1,
width: '40%',
},
];
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park',
},
];
function onChange(pagination, filters, sorter, extra) {
console.log('params', pagination, filters, sorter, extra);
}
return {
data,
columns,
onChange,
};
},
});
</script>

View File

@ -18,6 +18,7 @@
<Head />
<CustomFilterPanel />
<ResetFilter />
<filterSearchVue />
<RowSelectionAndOperation />
<RowSelectionCustom />
<RowSelection />
@ -61,6 +62,7 @@ import Summary from './summary.vue';
import Sticky from './sticky.vue';
import ResizableColumn from './resizable-column.vue';
import Responsive from './responsive.vue';
import filterSearchVue from './filter-search.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
@ -69,6 +71,7 @@ export default defineComponent({
CN,
US,
components: {
filterSearchVue,
Basic,
Ellipsis,
Ajax,

View File

@ -108,11 +108,12 @@ export interface FilterDropdownProps<RecordType> {
filterState?: FilterState<RecordType>;
filterMultiple: boolean;
filterMode?: 'menu' | 'tree';
filterSearch?: boolean;
filterSearch?: FilterSearchType;
columnKey: Key;
triggerFilter: (filterState: FilterState<RecordType>) => void;
locale: TableLocale;
getPopupContainer?: GetPopupContainer;
filterResetToDefaultFilteredValue?: boolean;
}
export default defineComponent<FilterDropdownProps<any>>({
@ -266,7 +267,11 @@ export default defineComponent<FilterDropdownProps<any>>({
triggerVisible(false);
}
searchValue.value = '';
if (props.column.filterResetToDefaultFilteredValue) {
filteredKeys.value = (props.column.defaultFilteredValue || []).map(key => String(key));
} else {
filteredKeys.value = [];
}
};
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
@ -432,7 +437,17 @@ export default defineComponent<FilterDropdownProps<any>>({
</>
);
};
const resetDisabled = computed(() => {
const selectedKeys = filteredKeys.value;
if (props.column.filterResetToDefaultFilteredValue) {
return isEqual(
(props.column.defaultFilteredValue || []).map(key => String(key)),
selectedKeys,
);
}
return selectedKeys.length === 0;
});
return () => {
const { tablePrefixCls, prefixCls, column, dropdownPrefixCls, locale, getPopupContainer } =
props;
@ -453,7 +468,6 @@ export default defineComponent<FilterDropdownProps<any>>({
} else if (filterDropdownRef.value) {
dropdownContent = filterDropdownRef.value;
} else {
const selectedKeys = filteredKeys.value as any;
dropdownContent = (
<>
{getFilterComponent()}
@ -461,7 +475,7 @@ export default defineComponent<FilterDropdownProps<any>>({
<Button
type="link"
size="small"
disabled={selectedKeys.length === 0}
disabled={resetDisabled.value}
onClick={() => onReset()}
>
{locale.filterReset}

View File

@ -1,7 +1,7 @@
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
import type { TableLocale } from '../../interface';
import type { FilterSearchType, TableLocale } from '../../interface';
import Input from '../../../input';
export default defineComponent({
@ -10,7 +10,7 @@ export default defineComponent({
props: {
value: String,
onChange: Function as PropType<(e: InputEvent) => void>,
filterSearch: Boolean,
filterSearch: [Boolean, Function] as PropType<FilterSearchType>,
tablePrefixCls: String,
locale: { type: Object as PropType<TableLocale>, default: undefined as TableLocale },
},

View File

@ -218,23 +218,25 @@ function useFilter<RecordType>({
const mergedFilterStates = computed(() => {
const collectedStates = collectFilterStates(mergedColumns.value, false);
const filteredKeysIsNotControlled = collectedStates.every(
({ filteredKeys }) => filteredKeys === undefined,
);
let filteredKeysIsAllNotControlled = true;
let filteredKeysIsAllControlled = true;
collectedStates.forEach(({ filteredKeys }) => {
if (filteredKeys !== undefined) {
filteredKeysIsAllNotControlled = false;
} else {
filteredKeysIsAllControlled = false;
}
});
// Return if not controlled
if (filteredKeysIsNotControlled) {
if (filteredKeysIsAllNotControlled) {
return filterStates.value;
}
const filteredKeysIsAllControlled = collectedStates.every(
({ filteredKeys }) => filteredKeys !== undefined,
);
devWarning(
filteredKeysIsNotControlled || filteredKeysIsAllControlled,
filteredKeysIsAllControlled,
'Table',
'`FilteredKeys` should all be controlled or not controlled.',
'Columns should all contain `filteredValue` or not contain `filteredValue`.',
);
return collectedStates;

View File

@ -20,6 +20,7 @@ import type { Ref } from 'vue';
import { computed } from 'vue';
import useState from '../../_util/hooks/useState';
import type { DefaultRecordType } from '../../vc-table/interface';
import KeyCode from '../../_util/KeyCode';
const ASCEND = 'ascend';
const DESCEND = 'descend';
@ -108,8 +109,8 @@ function collectSortStates<RecordType>(
function injectSorter<RecordType>(
prefixCls: string,
columns: ColumnsType<RecordType>,
sorterSates: SortState<RecordType>[],
triggerSorter: (sorterSates: SortState<RecordType>) => void,
sorterStates: SortState<RecordType>[],
triggerSorter: (sorterStates: SortState<RecordType>) => void,
defaultSortDirections: SortOrder[],
tableLocale?: TableLocale,
tableShowSorterTooltip?: boolean | TooltipProps,
@ -126,7 +127,7 @@ function injectSorter<RecordType>(
? tableShowSorterTooltip
: newColumn.showSorterTooltip;
const columnKey = getColumnKey(newColumn, columnPos);
const sorterState = sorterSates.find(({ key }) => key === columnKey);
const sorterState = sorterStates.find(({ key }) => key === columnKey);
const sorterOrder = sorterState ? sorterState.sortOrder : null;
const nextSortOrder = nextSortDirection(sortDirections, sorterOrder);
const upNode = sortDirections.includes(ASCEND) && (
@ -182,6 +183,7 @@ function injectSorter<RecordType>(
customHeaderCell: col => {
const cell = (column.customHeaderCell && column.customHeaderCell(col)) || {};
const originOnClick = cell.onClick;
const originOKeyDown = cell.onKeydown;
cell.onClick = (event: MouseEvent) => {
triggerSorter({
column,
@ -194,9 +196,29 @@ function injectSorter<RecordType>(
originOnClick(event);
}
};
cell.onKeydown = (event: KeyboardEvent) => {
if (event.keyCode === KeyCode.ENTER) {
triggerSorter({
column,
key: columnKey,
sortOrder: nextSortOrder,
multiplePriority: getMultiplePriority(column),
});
originOKeyDown?.(event);
}
};
// Inform the screen-reader so it can tell the visually impaired user which column is sorted
if (sorterOrder) {
if (sorterOrder === 'ascend') {
cell['aria-sort'] = 'ascending';
} else {
cell['aria-sort'] = 'descending';
}
}
cell.class = classNames(cell.class, `${prefixCls}-column-has-sorters`);
cell.tabindex = 0;
return cell;
},
};
@ -208,7 +230,7 @@ function injectSorter<RecordType>(
children: injectSorter(
prefixCls,
newColumn.children,
sorterSates,
sorterStates,
triggerSorter,
defaultSortDirections,
tableLocale,

View File

@ -157,6 +157,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
| customRender | Renderer of the table cell. The return value should be a VNode | Function({text, record, index}) {} | - | |
| dataIndex | Display field of the data record, support nest path by string array | string \| string\[] | - | |
| defaultFilteredValue | Default filtered values | string\[] | - | 1.5.0 |
| filterResetToDefaultFilteredValue | click the reset button, whether to restore the default filter | boolean | false | 3.3.0 |
| defaultSortOrder | Default order of sorted values: `'ascend'` `'descend'` `null` | string | - | |
| ellipsis | ellipsize cell content, not working with sorter and filters for now.<br />tableLayout would be `fixed` when `ellipsis` is true. | boolean | false | 1.5.0 |
| ellipsis | The ellipsis cell content, not working with sorter and filters for now.<br />tableLayout would be `fixed` when `ellipsis` is `true` or `{ showTitle?: boolean }` | boolean \| {showTitle?: boolean } | false | 3.0 |
@ -168,7 +169,7 @@ One of the Table `columns` prop for describing the table's columns, Column has t
| filterMode | To specify the filter interface | 'menu' \| 'tree' | 'menu' | 3.0 |
| filterMultiple | Whether multiple filters can be selected | boolean | `true` | |
| filters | Filter menu config | object\[] | - | |
| filterSearch | Whether to be searchable for filter menu | Boolean | false | 3.0 |
| filterSearch | Whether to be searchable for filter menu | boolean \| function(input, filter):boolean | false | boolean: 3.0 function: 3.3.0 |
| fixed | Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean\|string | `false` | |
| key | Unique key of this column, you can ignore this prop if you've set a unique `dataIndex` | string | - | |
| maxWidth | Drag the maximum width of the column, it will be affected by the automatic adjustment and distribution of the table width | number | - | 3.0 |

View File

@ -162,6 +162,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/f-SbcX2Lx/Table.svg
| customRender | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引 | Function({text, record, index, column}) {} | - | |
| dataIndex | 列数据在数据项中对应的路径,支持通过数组查询嵌套路径 | string \| string\[] | - | |
| defaultFilteredValue | 默认筛选值 | string\[] | - | 1.5.0 |
| filterResetToDefaultFilteredValue | 点击重置按钮的时候,是否恢复默认筛选值 | boolean | false | 3.3.0 |
| defaultSortOrder | 默认排序顺序 | `ascend` \| `descend` | - | |
| ellipsis | 超过宽度将自动省略,暂不支持和排序筛选一起使用。<br />设置为 `true``{ showTitle?: boolean }` 时,表格布局将变成 `tableLayout="fixed"`。 | boolean \| { showTitle?: boolean } | false | 3.0 |
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | VNode | - | |
@ -172,7 +173,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/f-SbcX2Lx/Table.svg
| filterMode | 指定筛选菜单的用户界面 | 'menu' \| 'tree' | 'menu' | 3.0 |
| filterMultiple | 是否多选 | boolean | true | |
| filters | 表头的筛选菜单项 | object\[] | - | |
| filterSearch | 筛选菜单项是否可搜索 | Boolean | false | 3.0 |
| filterSearch | 筛选菜单项是否可搜索 | boolean \| function(input, filter):boolean | false | boolean:3.0 function:3.3.0 |
| fixed | 列是否固定,可选 `true`(等效于 left) `'left'` `'right'` | boolean\|string | false | |
| key | Vue 需要的 key如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - | |
| maxWidth | 拖动列最大宽度,会受到表格自动调整分配宽度影响 | number | - | 3.0 |

View File

@ -73,7 +73,7 @@ export type ColumnTitle<RecordType> = VueNode | ((props: ColumnTitleProps<Record
export type FilterValue = (Key | boolean)[];
export type FilterKey = Key[] | null;
export type FilterSearchType = boolean | ((input: string, record: {}) => boolean);
export type FilterSearchType = boolean | ((input: string, record: ColumnFilterItem) => boolean);
export interface FilterConfirmProps {
closeDropdown: boolean;
}
@ -89,7 +89,8 @@ export interface FilterDropdownProps<RecordType> {
column: ColumnType<RecordType>;
}
export interface ColumnType<RecordType = DefaultRecordType> extends RcColumnType<RecordType> {
export interface ColumnType<RecordType = DefaultRecordType>
extends Omit<RcColumnType<RecordType>, 'title'> {
title?: ColumnTitle<RecordType>;
// Sorter
sorter?:
@ -114,11 +115,11 @@ export interface ColumnType<RecordType = DefaultRecordType> extends RcColumnType
defaultFilteredValue?: FilterValue | null;
filterIcon?: VueNode | ((opt: { filtered: boolean; column: ColumnType }) => VueNode);
filterMode?: 'menu' | 'tree';
filterSearch?: boolean;
filterSearch?: FilterSearchType;
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
filterDropdownVisible?: boolean;
onFilterDropdownVisibleChange?: (visible: boolean) => void;
filterResetToDefaultFilteredValue?: boolean;
// Responsive
responsive?: Breakpoint[];
}

View File

@ -2,7 +2,6 @@
@import '../../style/mixins/index';
@import './size';
@import './bordered';
@import './resize.less';
@table-prefix-cls: ~'@{ant-prefix}-table';
@tree-prefix-cls: ~'@{ant-prefix}-tree';
@ -217,6 +216,7 @@
// ============================ Sorter ============================
&-thead th.@{table-prefix-cls}-column-has-sorters {
outline: none;
cursor: pointer;
transition: all 0.3s;
@ -228,6 +228,10 @@
}
}
&:focus-visible {
color: @primary-color;
}
// https://github.com/ant-design/ant-design/issues/30969
&.@{table-prefix-cls}-cell-fix-left:hover,
&.@{table-prefix-cls}-cell-fix-right:hover {
@ -457,7 +461,7 @@
cursor: pointer;
transition: all 0.3s;
margin-inline-start: 100%;
padding-inline-start: @padding-xss;
padding-inline-start: (@table-padding-horizontal / 4);
.@{iconfont-css-prefix} {
color: @table-header-icon-color;

View File

@ -32,6 +32,11 @@
}
}
}
// https://github.com/ant-design/ant-design/issues/35167
.@{table-prefix-cls}-selection-column {
padding-inline-start: (@padding-horizontal / 4);
}
}
}
@ -44,13 +49,3 @@
// = Small =
// ================================================================
.table-size(~'small', @table-padding-vertical-sm, @table-padding-horizontal-sm, @table-font-size-sm);
.@{table-prefix-cls}-small {
.@{table-prefix-cls}-thead > tr > th {
background-color: @table-header-bg-sm;
}
.@{table-prefix-cls}-selection-column {
width: 46px;
min-width: 46px;
}
}