feat(skill-addresses): 地址检索升级为多关键词匹配

- 空格分隔多个关键词,全部命中(地址或技能名任一包含)才显示,便于在大量地址中收窄
- 搜索框加清除按钮 + 更清晰的占位提示
- 过滤时列表头显示「命中 / 总数」

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-22 17:55:44 +08:00
1 parent 26557b30a0
commit 7487395e44
1 file changed
+27 -8
+27 -8
View File
@@ -17,6 +17,7 @@ import {
Search,
Undo2,
Wand2,
X,
} from "lucide-react";
import { toast } from "sonner";
@@ -231,11 +232,19 @@ export function SkillAddressesPanel() {
}
};
const q = query.trim().toLowerCase();
const visible = useMemo(
() => (!q ? groups : groups.filter((g) => g.address.toLowerCase().includes(q) || g.occurrences.some((o) => o.skill.toLowerCase().includes(q)))),
[groups, q],
// 多关键词检索:空格分隔,每个关键词都要命中(地址或技能名任一包含)才显示。
// 这样「uat 4.cn」能同时收窄到既含 uat 又含 4.cn 的地址,应对地址过多的场景。
const tokens = useMemo(
() => query.trim().toLowerCase().split(/\s+/).filter(Boolean),
[query],
);
const visible = useMemo(() => {
if (tokens.length === 0) return groups;
return groups.filter((g) => {
const haystack = `${g.address} ${g.occurrences.map((o) => o.skill).join(" ")}`.toLowerCase();
return tokens.every((t) => haystack.includes(t));
});
}, [groups, tokens]);
const totalOccurrences = useMemo(() => groups.reduce((s, g) => s + g.count, 0), [groups]);
const canRollback = !!(lastBackupId || backups.length > 0);
@@ -332,15 +341,25 @@ export function SkillAddressesPanel() {
</button>
))}
</div>
<div className="relative w-full md:w-80">
<div className="relative w-full md:w-96">
<Search className="absolute left-3.5 top-1/2 size-4 -translate-y-1/2 text-slate-400" />
<input
type="text"
placeholder="搜索地址 / 技能名…"
placeholder="按关键词检索地址 / 技能名(空格分隔多个,全部命中)"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full rounded-xl border border-slate-200 bg-slate-50/50 py-2 pl-10 pr-4 text-sm transition-all hover:border-slate-300 focus:border-blue-500 focus:outline-none focus:ring-4 focus:ring-blue-500/5"
className="w-full rounded-xl border border-slate-200 bg-slate-50/50 py-2 pl-10 pr-9 text-sm transition-all hover:border-slate-300 focus:border-blue-500 focus:outline-none focus:ring-4 focus:ring-blue-500/5"
/>
{query && (
<button
type="button"
onClick={() => setQuery("")}
title="清除"
className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600"
>
<X className="size-4" />
</button>
)}
</div>
</div>
@@ -422,7 +441,7 @@ export function SkillAddressesPanel() {
{/* 地址列表 */}
<div className="overflow-hidden rounded-2xl border border-slate-200/60 bg-white shadow-sm">
<div className="flex items-center justify-between border-b border-slate-200/60 bg-slate-50/70 px-4 py-2.5 text-xs font-semibold text-slate-500">
<span>地址({visible.length})</span>
<span>地址({visible.length}{tokens.length > 0 ? ` / ${groups.length}` : ""})</span>
{pendingCount > 0 && (
<button
type="button"