From 79ff7ac2dba4ab5cf01241ceef072f2c4be20e12 Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Mon, 7 Mar 2022 22:24:38 +0800
Subject: [PATCH] feat: table add tree filter
---
components/_util/EventInterface.ts | 5 +
components/style/themes/default.less | 2 +
components/table/Table.tsx | 6 +-
.../table/hooks/useFilter/FilterDropdown.tsx | 249 +++++++++++++-----
.../table/hooks/useFilter/FilterSearch.tsx | 38 +++
components/table/hooks/useFilter/index.tsx | 6 +-
components/table/hooks/usePagination.ts | 4 +-
components/table/hooks/useSelection.tsx | 172 ++++++++----
components/table/index.tsx | 11 +-
components/table/interface.tsx | 5 +-
components/table/style/bordered.less | 14 +-
components/table/style/index.less | 88 ++++++-
components/table/style/index.tsx | 2 +
components/table/style/resize.less | 28 --
components/table/style/rtl.less | 14 +-
components/table/style/size.less | 4 +-
components/vc-table/Body/BodyRow.tsx | 19 +-
components/vc-table/Body/ExpandedRow.tsx | 37 +--
components/vc-table/Body/MeasureCell.tsx | 2 +-
components/vc-table/Body/index.tsx | 30 ++-
components/vc-table/Cell/index.tsx | 94 +++++--
components/vc-table/ColGroup.tsx | 3 +-
components/vc-table/Footer/Cell.tsx | 6 +-
components/vc-table/Table.tsx | 35 ++-
components/vc-table/constant.ts | 1 +
components/vc-table/context/BodyContext.tsx | 4 -
.../vc-table/context/ExpandedRowContext.tsx | 18 ++
components/vc-table/context/HoverContext.tsx | 21 ++
components/vc-table/context/StickyContext.tsx | 13 +
components/vc-table/hooks/useColumns.tsx | 50 +++-
.../vc-table/hooks/useFlattenRecords.ts | 14 +-
components/vc-table/index.ts | 13 +-
components/vc-table/interface.ts | 9 +-
components/vc-table/stickyScrollBar.tsx | 3 +
components/vc-table/utils/legacyUtil.ts | 50 ++--
35 files changed, 770 insertions(+), 300 deletions(-)
create mode 100644 components/table/hooks/useFilter/FilterSearch.tsx
delete mode 100644 components/table/style/resize.less
create mode 100644 components/vc-table/constant.ts
create mode 100644 components/vc-table/context/ExpandedRowContext.tsx
create mode 100644 components/vc-table/context/HoverContext.tsx
create mode 100644 components/vc-table/context/StickyContext.tsx
diff --git a/components/_util/EventInterface.ts b/components/_util/EventInterface.ts
index b9d0bd019..7d6f193c9 100644
--- a/components/_util/EventInterface.ts
+++ b/components/_util/EventInterface.ts
@@ -6,5 +6,10 @@ export type ChangeEvent = Event & {
value?: string | undefined;
};
};
+export type CheckboxChangeEvent = Event & {
+ target: {
+ checked?: boolean;
+ };
+};
export type EventHandler = (...args: any[]) => void;
diff --git a/components/style/themes/default.less b/components/style/themes/default.less
index 1dfe4c221..8b54deeb9 100644
--- a/components/style/themes/default.less
+++ b/components/style/themes/default.less
@@ -632,6 +632,8 @@
// Sorter
// Legacy: `table-header-sort-active-bg` is used for hover not real active
@table-header-sort-active-bg: rgba(0, 0, 0, 0.04);
+@table-fixed-header-sort-active-bg: hsv(0, 0, 96%);
+
// Filter
@table-header-filter-active-bg: rgba(0, 0, 0, 0.04);
@table-filter-btns-bg: inherit;
diff --git a/components/table/Table.tsx b/components/table/Table.tsx
index 4619d5472..607542fff 100644
--- a/components/table/Table.tsx
+++ b/components/table/Table.tsx
@@ -520,7 +520,6 @@ const InteralTable = defineComponent<
expandType,
childrenColumnName,
locale: tableLocale,
- expandIconColumnIndex,
getPopupContainer: computed(() => props.getPopupContainer),
});
@@ -581,8 +580,11 @@ const InteralTable = defineComponent<
const renderPagination = (position: string) => (
);
diff --git a/components/table/hooks/useFilter/FilterDropdown.tsx b/components/table/hooks/useFilter/FilterDropdown.tsx
index 2c24aaf2d..92eba8ccf 100644
--- a/components/table/hooks/useFilter/FilterDropdown.tsx
+++ b/components/table/hooks/useFilter/FilterDropdown.tsx
@@ -15,10 +15,15 @@ import type {
} from '../../interface';
import FilterDropdownMenuWrapper from './FilterWrapper';
import type { FilterState } from '.';
+import { flattenKeys } from '.';
import { computed, defineComponent, onBeforeUnmount, ref, shallowRef, watch } from 'vue';
import classNames from '../../../_util/classNames';
import useConfigInject from '../../../_util/hooks/useConfigInject';
import { useInjectSlots } from '../../context';
+import type { DataNode, EventDataNode } from '../../../tree';
+import type { CheckboxChangeEvent, EventHandler } from '../../../_util/EventInterface';
+import FilterSearch from './FilterSearch';
+import Tree from '../../../tree';
const { SubMenu, Item: MenuItem } = Menu;
@@ -26,40 +31,26 @@ function hasSubMenu(filters: ColumnFilterItem[]) {
return filters.some(({ children }) => children && children.length > 0);
}
+function searchValueMatched(searchValue: string, text: any) {
+ if (typeof text === 'string' || typeof text === 'number') {
+ return text?.toString().toLowerCase().includes(searchValue.trim().toLowerCase());
+ }
+ return false;
+}
+
function renderFilterItems({
filters,
prefixCls,
filteredKeys,
filterMultiple,
- locale,
+ searchValue,
}: {
filters: ColumnFilterItem[];
prefixCls: string;
filteredKeys: Key[];
filterMultiple: boolean;
- locale: TableLocale;
+ searchValue: string;
}) {
- if (filters.length === 0) {
- // wrapped with
to avoid react warning
- // https://github.com/ant-design/ant-design/issues/25979
- return (
-
- );
- }
return filters.map((filter, index) => {
const key = String(filter.value);
@@ -75,7 +66,7 @@ function renderFilterItems({
prefixCls,
filteredKeys,
filterMultiple,
- locale,
+ searchValue,
})}
);
@@ -83,12 +74,16 @@ function renderFilterItems({
const Component = filterMultiple ? Checkbox : Radio;
- return (
+ const item = (
);
+ if (searchValue.trim()) {
+ return searchValueMatched(searchValue, filter.text) ? item : undefined;
+ }
+ return item;
});
}
@@ -99,6 +94,8 @@ export interface FilterDropdownProps {
column: ColumnType;
filterState?: FilterState;
filterMultiple: boolean;
+ filterMode?: 'menu' | 'tree';
+ filterSearch?: boolean;
columnKey: Key;
triggerFilter: (filterState: FilterState) => void;
locale: TableLocale;
@@ -114,6 +111,8 @@ export default defineComponent>({
'column',
'filterState',
'filterMultiple',
+ 'filterMode',
+ 'filterSearch',
'columnKey',
'triggerFilter',
'locale',
@@ -121,6 +120,8 @@ export default defineComponent>({
] as any,
setup(props, { slots }) {
const contextSlots = useInjectSlots();
+ const filterMode = computed(() => props.filterMode ?? 'menu');
+ const filterSearch = computed(() => props.filterSearch ?? false);
const filterDropdownVisible = computed(() => props.column.filterDropdownVisible);
const visible = ref(false);
const filtered = computed(
@@ -168,9 +169,20 @@ export default defineComponent>({
filteredKeys.value = selectedKeys;
};
+ const onCheck = (keys: Key[], { node, checked }: { node: EventDataNode; checked: boolean }) => {
+ if (!props.filterMultiple) {
+ onSelectKeys({ selectedKeys: checked && node.key ? [node.key] : [] });
+ } else {
+ onSelectKeys({ selectedKeys: keys as Key[] });
+ }
+ };
+
watch(
propFilteredKeys,
() => {
+ if (!visible.value) {
+ return;
+ }
onSelectKeys({ selectedKeys: propFilteredKeys.value || [] });
},
{ immediate: true },
@@ -193,6 +205,18 @@ export default defineComponent>({
clearTimeout(openRef.value);
});
+ const searchValue = ref('');
+ const onSearch: EventHandler = e => {
+ const { value } = e.target;
+ searchValue.value = value;
+ };
+ // clear search value after close filter dropdown
+ watch(visible, () => {
+ if (!visible.value) {
+ searchValue.value = '';
+ }
+ });
+
// ======================= Submit ========================
const internalTriggerFilter = (keys: Key[] | undefined | null) => {
const { column, columnKey, filterState } = props;
@@ -218,9 +242,8 @@ export default defineComponent>({
};
const onReset = () => {
+ searchValue.value = '';
filteredKeys.value = [];
- triggerVisible(false);
- internalTriggerFilter([]);
};
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
@@ -244,20 +267,143 @@ export default defineComponent>({
};
const { direction } = useConfigInject('', props);
- return () => {
- const {
- tablePrefixCls,
- prefixCls,
- column,
- dropdownPrefixCls,
- filterMultiple,
- locale,
- getPopupContainer,
- } = props;
- // ======================== Style ========================
- const dropdownMenuClass = classNames({
- [`${dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(column.filters || []),
+
+ const onCheckAll = (e: CheckboxChangeEvent) => {
+ if (e.target.checked) {
+ const allFilterKeys = flattenKeys(props.column?.filters).map(key => String(key));
+ filteredKeys.value = allFilterKeys;
+ } else {
+ filteredKeys.value = [];
+ }
+ };
+
+ const getTreeData = ({ filters }: { filters?: ColumnFilterItem[] }) =>
+ (filters || []).map((filter, index) => {
+ const key = String(filter.value);
+ const item: DataNode = {
+ title: filter.text,
+ key: filter.value !== undefined ? key : index,
+ };
+ if (filter.children) {
+ item.children = getTreeData({ filters: filter.children });
+ }
+ return item;
});
+ // ======================== Style ========================
+ const dropdownMenuClass = computed(() =>
+ classNames({
+ [`${props.dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(
+ props.column.filters || [],
+ ),
+ }),
+ );
+ const getFilterComponent = () => {
+ const selectedKeys = filteredKeys.value;
+ const {
+ column,
+ locale,
+ tablePrefixCls,
+ filterMultiple,
+ dropdownPrefixCls,
+ getPopupContainer,
+ prefixCls,
+ } = props;
+ if ((column.filters || []).length === 0) {
+ return (
+
+ );
+ }
+ if (filterMode.value === 'tree') {
+ return (
+ <>
+
+
+ {filterMultiple ? (
+
+ {locale.filterCheckall}
+
+ ) : null}
+
+ >
+ );
+ }
+ return (
+ <>
+
+
+ >
+ );
+ };
+
+ return () => {
+ const { tablePrefixCls, prefixCls, column, dropdownPrefixCls, locale, getPopupContainer } =
+ props;
let dropdownContent;
@@ -278,28 +424,7 @@ export default defineComponent>({
const selectedKeys = filteredKeys.value as any;
dropdownContent = (
<>
-
+ {getFilterComponent()}