feat: 客户多选筛选、统计报表里程与监控看板数据一致
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 资产管理按客户筛选改为多选(支持同时选多个客户) - 新增 MultiSearchSelect 组件(搜索+标签+复选框) - 统计报表 todayTotal 改用监控缓存数据,与里程看板一致 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
src/components/MultiSearchSelect.tsx
Normal file
88
src/components/MultiSearchSelect.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
export function MultiSearchSelect({ value, onChange, options, placeholder, className }: {
|
||||
value: string[];
|
||||
onChange: (v: string[]) => void;
|
||||
options: string[];
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
}, []);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!query) return options;
|
||||
const q = query.toLowerCase();
|
||||
return options.filter((o) => o.toLowerCase().includes(q));
|
||||
}, [options, query]);
|
||||
|
||||
const toggle = (name: string) => {
|
||||
onChange(value.includes(name) ? value.filter(c => c !== name) : [...value, name]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
{value.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mb-1.5">
|
||||
{value.map(c => (
|
||||
<span key={c} className="inline-flex items-center gap-0.5 px-1.5 py-0.5 bg-emerald-100 text-emerald-700 rounded text-[10px] font-medium">
|
||||
{c}
|
||||
<button onClick={() => toggle(c)} className="hover:text-red-500 ml-0.5 leading-none">×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`flex items-center bg-white border border-gray-200 rounded-lg shadow-sm cursor-pointer transition-all focus-within:ring-2 focus-within:ring-emerald-500/20 focus-within:border-emerald-500 ${className || 'text-xs py-1.5 px-2'}`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="flex-1 outline-none bg-transparent min-w-0 text-inherit"
|
||||
placeholder={value.length > 0 ? `已选 ${value.length} 个客户` : placeholder}
|
||||
value={open ? query : ''}
|
||||
onChange={(e) => { setQuery(e.target.value); if (!open) setOpen(true); }}
|
||||
onFocus={() => { setOpen(true); setQuery(''); }}
|
||||
/>
|
||||
<ChevronDown size={14} className={`text-gray-400 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
{open && (
|
||||
<div className="absolute z-50 left-0 right-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-48 overflow-auto">
|
||||
{value.length > 0 && (
|
||||
<div
|
||||
className="px-2 py-1.5 text-xs text-gray-400 cursor-pointer hover:bg-gray-50 border-b border-gray-100"
|
||||
onClick={() => onChange([])}
|
||||
>
|
||||
清除全部已选
|
||||
</div>
|
||||
)}
|
||||
{filtered.map(o => (
|
||||
<div
|
||||
key={o}
|
||||
className={`px-2 py-1.5 text-xs cursor-pointer hover:bg-emerald-50 transition-colors flex items-center gap-1.5 ${value.includes(o) ? 'bg-emerald-50 text-emerald-700 font-medium' : 'text-gray-700'}`}
|
||||
onClick={() => toggle(o)}
|
||||
>
|
||||
<span className={`w-3.5 h-3.5 rounded border flex items-center justify-center shrink-0 ${value.includes(o) ? 'bg-emerald-500 border-emerald-500 text-white' : 'border-gray-300'}`}>
|
||||
{value.includes(o) && <span className="text-[9px]">✓</span>}
|
||||
</span>
|
||||
{o}
|
||||
</div>
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<div className="px-2 py-3 text-xs text-gray-400 text-center">无匹配项</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user