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,
+ };
+}