import { useState, useRef, useEffect, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { ChevronDown, X, AlertTriangle } from 'lucide-react'; interface Props { allPlates: string[]; selected: string[]; onChange: (plates: string[]) => void; placeholder?: string; } function parseInput(text: string): string[] { return text .split(/[\s,;,;、]+/) .map(s => s.trim()) .filter(Boolean); } export default function PlateMultiSelect({ allPlates, selected, onChange, placeholder = '按车牌(可多选/粘贴)' }: Props) { const [isOpen, setIsOpen] = useState(false); const [text, setText] = useState(''); const [search, setSearch] = useState(''); const [unmatched, setUnmatched] = useState([]); const wrapRef = useRef(null); const allSet = useMemo(() => new Set(allPlates), [allPlates]); useEffect(() => { if (!isOpen) return; const handler = (e: MouseEvent) => { if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setIsOpen(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [isOpen]); const filtered = useMemo(() => { if (!search) return allPlates.slice(0, 200); const q = search.toLowerCase(); return allPlates.filter(p => p.toLowerCase().includes(q)).slice(0, 200); }, [allPlates, search]); const selectedSet = useMemo(() => new Set(selected), [selected]); const apply = (input: string) => { const tokens = parseInput(input); if (tokens.length === 0) return; const matched: string[] = []; const missed: string[] = []; const seen = new Set(selected); for (const t of tokens) { if (allSet.has(t)) { if (!seen.has(t)) { matched.push(t); seen.add(t); } } else { missed.push(t); } } if (matched.length > 0) onChange([...selected, ...matched]); setUnmatched(missed); setText(''); }; const togglePlate = (plate: string) => { if (selectedSet.has(plate)) { onChange(selected.filter(p => p !== plate)); } else { onChange([...selected, plate]); } }; const removePlate = (plate: string) => { onChange(selected.filter(p => p !== plate)); }; const clearAll = () => { onChange([]); setUnmatched([]); setText(''); }; const display = selected.length === 0 ? placeholder : selected.length === 1 ? selected[0] : `${selected[0]} 等 ${selected.length} 个车牌`; return (
setIsOpen(o => !o)} className={`w-full bg-slate-50 rounded-lg py-1.5 px-2 text-[10px] font-bold cursor-pointer flex items-center justify-between gap-1 ${selected.length > 0 ? 'text-blue-600 ring-1 ring-blue-200' : 'text-slate-600'}`} > {display}
{isOpen && (