From b893fd558a9637ad485ab3427c7b850bc669bcd2 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 18 Jun 2026 09:44:52 +0800 Subject: [PATCH] Add FilterDropdown component and column filter props factory src/app/table-filters.tsx renders type-specific filter UIs (text search, category checkbox list, date range) and exposes buildColumnFilterProps to attach antd controlled filter props (filterDropdown/onFilter/filteredValue) to a column. Verified against antd v6.4.3 type defs. Co-Authored-By: Claude --- src/app/table-filters.tsx | 139 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/app/table-filters.tsx diff --git a/src/app/table-filters.tsx b/src/app/table-filters.tsx new file mode 100644 index 0000000..6a190f3 --- /dev/null +++ b/src/app/table-filters.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { Button, Input, Checkbox, DatePicker, Space } from "antd"; +import dayjs from "dayjs"; +import type { ColumnType } from "antd/es/table"; +import type { FilterDropdownProps } from "antd/es/table/interface"; +import { inferFilterType, distinctValues, matchesFilter } from "./table-filters"; +import type { DataRow, FilterType } from "./table-filters"; + +const DATE_FMT = "YYYY-MM-DD"; + +/** 解析受控 selectedKeys 里的 "start|end" 编码为 RangePicker 值。 */ +function decodeRange(keys: React.Key[]): [dayjs.Dayjs | null, dayjs.Dayjs | null] { + const raw = keys[0] != null ? String(keys[0]) : ""; + const sep = raw.indexOf("|"); + if (sep < 0) return [null, null]; + const s = raw.slice(0, sep); + const e = raw.slice(sep + 1); + return [s ? dayjs(s, DATE_FMT) : null, e ? dayjs(e, DATE_FMT) : null]; +} + +export function FilterDropdown({ + type, + distinct, + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, +}: FilterDropdownProps & { + type: FilterType; + distinct: string[]; +}): React.ReactElement { + if (type === "date") { + const [start, end] = decodeRange(selectedKeys); + return ( +
+ { + if (!dates || dates[0] == null || dates[1] == null) { + setSelectedKeys([]); + } else { + const s = dates[0].format(DATE_FMT); + const e = dates[1].format(DATE_FMT); + setSelectedKeys([`${s}|${e}`]); // 单个编码键,供 onFilter 解码 + } + }} + /> + + + + +
+ ); + } + + if (type === "category") { + return ( +
+ setSelectedKeys(vals as React.Key[])} + options={distinct} + /> + + + + +
+ ); + } + + // text + const textValue = selectedKeys[0] != null ? String(selectedKeys[0]) : ""; + return ( +
+ setSelectedKeys(e.target.value ? [e.target.value] : [])} + onPressEnter={() => confirm()} + style={{ marginBottom: 8, display: "block" }} + allowClear + /> + + + + +
+ ); +} + +/** 为某列构造 antd 受控筛选属性(filterDropdown / onFilter / filteredValue)。 */ +export function buildColumnFilterProps( + key: string, + data: DataRow[], + filteredValue: React.Key[] | null +): Partial> { + const type = inferFilterType(key, data); + const distinct = type === "category" ? distinctValues(key, data) : []; + return { + filterDropdown: (props) => ( + + ), + onFilter: (value, record) => matchesFilter(type, record[key], String(value)), + filteredValue: filteredValue ?? null, + }; +}