feat: table add filterResetToDefaultFilteredValue & filterSearch funcion
parent
a4b6c0aee4
commit
37d35f7801
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue