feat(skill-addresses): 地址检索升级为多关键词匹配
- 空格分隔多个关键词,全部命中(地址或技能名任一包含)才显示,便于在大量地址中收窄 - 搜索框加清除按钮 + 更清晰的占位提示 - 过滤时列表头显示「命中 / 总数」 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
26557b30a0
commit
7487395e44
1 file changed
+27
-8
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user