Add search history with AutoComplete dropdown
- Show last 10 search records on input focus with clock icon - Filter history as user types - Click history item to search directly - Deduplicate and persist to localStorage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,9 @@ import {
|
|||||||
Empty,
|
Empty,
|
||||||
Modal,
|
Modal,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
AutoComplete,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { SearchOutlined, SettingOutlined, HolderOutlined } from "@ant-design/icons";
|
import { SearchOutlined, SettingOutlined, HolderOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||||
import type { ColumnsType } from "antd/es/table";
|
import type { ColumnsType } from "antd/es/table";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
@@ -300,6 +301,29 @@ function ColumnSettingsModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Search History ─── */
|
||||||
|
|
||||||
|
const HISTORY_KEY = "web-table-search-history";
|
||||||
|
const MAX_HISTORY = 10;
|
||||||
|
|
||||||
|
function loadHistory(): string[] {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(HISTORY_KEY);
|
||||||
|
return saved ? JSON.parse(saved) : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveHistoryToStorage(history: string[]) {
|
||||||
|
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToHistory(value: string, history: string[]): string[] {
|
||||||
|
const filtered = history.filter((h) => h !== value);
|
||||||
|
return [value, ...filtered].slice(0, MAX_HISTORY);
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Search Bar (isolated) ─── */
|
/* ─── Search Bar (isolated) ─── */
|
||||||
|
|
||||||
function SearchBar({
|
function SearchBar({
|
||||||
@@ -313,17 +337,47 @@ function SearchBar({
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
resultCount: number;
|
resultCount: number;
|
||||||
searched: boolean;
|
searched: boolean;
|
||||||
onSearch: (workshopNo: string) => void;
|
onSearch: (workshopNo: string) => Promise<void>;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
onOpenColumnSettings: () => void;
|
onOpenColumnSettings: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
const [history, setHistory] = useState<string[]>(() => loadHistory());
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const filteredHistory = inputValue
|
||||||
|
? history.filter((h) => h.toLowerCase().includes(inputValue.toLowerCase()))
|
||||||
|
: history;
|
||||||
|
|
||||||
|
const options = filteredHistory.map((h) => ({
|
||||||
|
value: h,
|
||||||
|
label: (
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
|
<ClockCircleOutlined style={{ color: "#bbb" }} />
|
||||||
|
{h}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
const handleSearch = useCallback(() => {
|
const handleSearch = useCallback(() => {
|
||||||
if (inputValue.trim()) {
|
const val = inputValue.trim();
|
||||||
onSearch(inputValue.trim());
|
if (!val) return;
|
||||||
}
|
const updated = addToHistory(val, history);
|
||||||
}, [inputValue, onSearch]);
|
setHistory(updated);
|
||||||
|
saveHistoryToStorage(updated);
|
||||||
|
onSearch(val);
|
||||||
|
}, [inputValue, history, onSearch]);
|
||||||
|
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(val: string) => {
|
||||||
|
setInputValue(val);
|
||||||
|
const updated = addToHistory(val, history);
|
||||||
|
setHistory(updated);
|
||||||
|
saveHistoryToStorage(updated);
|
||||||
|
onSearch(val);
|
||||||
|
},
|
||||||
|
[history, onSearch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -339,15 +393,23 @@ function SearchBar({
|
|||||||
</Title>
|
</Title>
|
||||||
<Space>
|
<Space>
|
||||||
<Space.Compact>
|
<Space.Compact>
|
||||||
<Input
|
<AutoComplete
|
||||||
prefix={<SearchOutlined />}
|
open={open && filteredHistory.length > 0}
|
||||||
placeholder="请输入车间号,如 R05697"
|
options={options}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(val) => setInputValue(val)}
|
||||||
onPressEnter={handleSearch}
|
onSelect={handleSelect}
|
||||||
allowClear
|
onFocus={() => setOpen(true)}
|
||||||
|
onBlur={() => setOpen(false)}
|
||||||
style={{ width: 320 }}
|
style={{ width: 320 }}
|
||||||
/>
|
>
|
||||||
|
<Input
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
placeholder="请输入车间号,如 R05697"
|
||||||
|
onPressEnter={handleSearch}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</AutoComplete>
|
||||||
<Button type="primary" onClick={handleSearch} loading={loading}>
|
<Button type="primary" onClick={handleSearch} loading={loading}>
|
||||||
查询
|
查询
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user