Wire controlled column filters and clear-filters button

buildTableColumns now attaches antd controlled filter props (via
buildColumnFilterProps) to columns marked filterable; filters state is
controlled via Table onChange. A 'clear filters' button in the search bar
resets all filters and is disabled when none are active. Filters and row
lock reset on search/clear.

Also renames the component module table-filters.tsx -> table-filter-ui.tsx
to avoid a same-base-name resolution collision with the logic module
table-filters.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-06-18 09:52:51 +08:00
parent f8914fb287
commit 2f3cd9fa06
2 changed files with 34 additions and 4 deletions

View File

@@ -39,6 +39,7 @@ import {
} from "@dnd-kit/sortable"; } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import { nextLockedRow, type DataRow } from "./table-filters"; import { nextLockedRow, type DataRow } from "./table-filters";
import { buildColumnFilterProps } from "./table-filter-ui";
const { Title, Text } = Typography; const { Title, Text } = Typography;
@@ -141,14 +142,16 @@ function saveConfig(config: ColumnConfig[]) {
function buildTableColumns( function buildTableColumns(
config: ColumnConfig[], config: ColumnConfig[],
dataColumns: string[] dataColumns: string[],
data: DataRow[],
filters: Record<string, React.Key[]>
): ColumnsType<DataRow> { ): ColumnsType<DataRow> {
const available = new Set(dataColumns); const available = new Set(dataColumns);
return config return config
.filter((c) => c.visible && available.has(c.key)) .filter((c) => c.visible && available.has(c.key))
.map((c) => { .map((c) => {
const def = COLUMN_DEFS.find((d) => d.key === c.key); const def = COLUMN_DEFS.find((d) => d.key === c.key);
return { const base = {
title: c.key, title: c.key,
dataIndex: c.key, dataIndex: c.key,
key: c.key, key: c.key,
@@ -157,6 +160,9 @@ function buildTableColumns(
render: (val: string | number | null) => render: (val: string | number | null) =>
val === null || val === undefined ? "" : String(val), val === null || val === undefined ? "" : String(val),
}; };
return c.filterable
? { ...base, ...buildColumnFilterProps(c.key, data, filters[c.key] ?? null) }
: base;
}); });
} }
@@ -367,6 +373,8 @@ function SearchBar({
onOpenColumnSettings, onOpenColumnSettings,
exporting, exporting,
onExport, onExport,
hasActiveFilters,
onClearFilters,
}: { }: {
loading: boolean; loading: boolean;
resultCount: number; resultCount: number;
@@ -376,6 +384,8 @@ function SearchBar({
onOpenColumnSettings: () => void; onOpenColumnSettings: () => void;
exporting: boolean; exporting: boolean;
onExport: () => void; onExport: () => void;
hasActiveFilters: boolean;
onClearFilters: () => void;
}) { }) {
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [history, setHistory] = useState<string[]>(() => loadHistory()); const [history, setHistory] = useState<string[]>(() => loadHistory());
@@ -461,6 +471,13 @@ function SearchBar({
> >
</Button> </Button>
<Button
icon={<FilterOutlined />}
onClick={onClearFilters}
disabled={!hasActiveFilters}
>
</Button>
</Space> </Space>
{searched && !loading && ( {searched && !loading && (
<Text type="secondary" style={{ marginLeft: 16 }}> <Text type="secondary" style={{ marginLeft: 16 }}>
@@ -483,6 +500,7 @@ export default function Home() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const [lockedRowKey, setLockedRowKey] = useState<string | null>(null); const [lockedRowKey, setLockedRowKey] = useState<string | null>(null);
const [filters, setFilters] = useState<Record<string, React.Key[]>>({});
// Persist config // Persist config
useEffect(() => { useEffect(() => {
@@ -505,6 +523,7 @@ export default function Home() {
setData(json.data); setData(json.data);
setSearched(true); setSearched(true);
setLockedRowKey(null); setLockedRowKey(null);
setFilters({});
} catch { } catch {
setError("网络请求失败"); setError("网络请求失败");
} finally { } finally {
@@ -518,6 +537,7 @@ export default function Home() {
setSearched(false); setSearched(false);
setError(null); setError(null);
setLockedRowKey(null); setLockedRowKey(null);
setFilters({});
}, []); }, []);
const handleExport = useCallback(async () => { const handleExport = useCallback(async () => {
@@ -551,9 +571,14 @@ export default function Home() {
}, []); }, []);
const tableColumns = useMemo( const tableColumns = useMemo(
() => buildTableColumns(columnConfig, columns), () => buildTableColumns(columnConfig, columns, data, filters),
[columnConfig, columns] [columnConfig, columns, data, filters]
); );
const hasActiveFilters = useMemo(
() => Object.values(filters).some((arr) => arr && arr.length > 0),
[filters]
);
const clearFilters = useCallback(() => setFilters({}), []);
const totalWidth = useMemo( const totalWidth = useMemo(
() => tableColumns.reduce((sum, col) => sum + (col.width as number), 0), () => tableColumns.reduce((sum, col) => sum + (col.width as number), 0),
[tableColumns] [tableColumns]
@@ -574,6 +599,8 @@ export default function Home() {
onOpenColumnSettings={() => setModalOpen(true)} onOpenColumnSettings={() => setModalOpen(true)}
exporting={exporting} exporting={exporting}
onExport={handleExport} onExport={handleExport}
hasActiveFilters={hasActiveFilters}
onClearFilters={clearFilters}
/> />
<div style={{ flex: 1, minHeight: 0 }}> <div style={{ flex: 1, minHeight: 0 }}>
@@ -624,6 +651,9 @@ export default function Home() {
rowClassName={(record) => rowClassName={(record) =>
String(record.ID) === lockedRowKey ? "row-locked" : "" String(record.ID) === lockedRowKey ? "row-locked" : ""
} }
onChange={(_pagination, tableFilters) =>
setFilters(tableFilters as Record<string, React.Key[]>)
}
bordered bordered
size="small" size="small"
pagination={false} pagination={false}