feat(ui): 品类筛选框升级为可输入关键词过滤的 Combobox
【问题】 询价发布助手筛选工具栏的品类下拉框有 53+ 个选项,用户难以快速定位。 【改动】 - 把品类 filter 从统一 .map(select) 里拆出,改成自定义 Combobox: - input + 浮动下拉面板 (z-30) - 关键词过滤 (toLowerCase + includes 模糊匹配,中文友好) - 键盘导航: ↓/↑ 高亮 / Enter 选中 / Esc 关闭 / Backspace 空时清空筛选 - 鼠标: hover 高亮 / click 选中 / click outside 关闭 (useEffect + ref) - 选中项右侧显示 ✓ - 已选非默认时显示 × 清空按钮 - 其他 5 个 filter (公告日期/星级/甲方级别/来源平台/负责人) 保持原生 select 不变 - 状态: catInput / catOpen / catHighlightIdx / catRef,useMemo 缓存过滤结果 【体验】 - 输入'钢' → 立即显示 钢材/钢格栅 等候选 - 输入'电缆' → 显示 电线电缆 - 空 input + Enter → 选中'全部品类' 等价清空筛选 - 已选'钢材'显示在 placeholder + 右侧 × 按钮 【部署】 web 重新构建 + sync .next 到生产 + PM2 重启 pingtai-web
This commit is contained in:
parent
1a92a3c095
commit
a822d7782b
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useEffect, useState, useCallback, useRef, useMemo } from 'react'
|
||||
import { API_BASE } from '@/lib/apiBase'
|
||||
|
||||
interface Lead {
|
||||
@ -115,6 +115,30 @@ export default function InquiryPage({ onLeadDetail }: { onLeadDetail?: (id: stri
|
||||
const [fLevel, setFLevel] = useState('全部级别')
|
||||
const [fCat, setFCat] = useState('全部品类')
|
||||
const [catOptions, setCatOptions] = useState<string[]>(['全部品类'])
|
||||
const [catInput, setCatInput] = useState('')
|
||||
const [catOpen, setCatOpen] = useState(false)
|
||||
const [catHighlightIdx, setCatHighlightIdx] = useState(0)
|
||||
const catRef = useRef<HTMLDivElement>(null)
|
||||
const filteredCatOptions = useMemo(() => {
|
||||
const q = catInput.trim().toLowerCase()
|
||||
if (!q) return catOptions
|
||||
return catOptions.filter((c) => c.toLowerCase().includes(q))
|
||||
}, [catInput, catOptions])
|
||||
useEffect(() => {
|
||||
if (!catOpen) return
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (catRef.current && !catRef.current.contains(e.target as Node)) setCatOpen(false)
|
||||
}
|
||||
document.addEventListener('mousedown', handler)
|
||||
return () => document.removeEventListener('mousedown', handler)
|
||||
}, [catOpen])
|
||||
useEffect(() => { setCatHighlightIdx(0) }, [catInput, catOpen])
|
||||
const selectCat = (cat: string) => {
|
||||
setFCat(cat)
|
||||
setCatInput('')
|
||||
setCatOpen(false)
|
||||
setCatHighlightIdx(0)
|
||||
}
|
||||
const [fSource, setFSource] = useState('全部来源')
|
||||
const [fPerson, setFPerson] = useState('全部人员')
|
||||
|
||||
@ -402,7 +426,6 @@ export default function InquiryPage({ onLeadDetail }: { onLeadDetail?: (id: stri
|
||||
{ label: '公告日期', value: fTime, set: setFTime, opts: ['全部日期', '今天', '昨天', '本周', '本月'] },
|
||||
{ label: '星级', value: fStar, set: setFStar, opts: ['全部星级', '5星', '4星', '3星', '2星及以下'] },
|
||||
{ label: '甲方级别', value: fLevel, set: setFLevel, opts: LEVEL_OPTIONS },
|
||||
{ label: '品类', value: fCat, set: setFCat, opts: catOptions },
|
||||
{ label: '来源平台', value: fSource, set: setFSource, opts: SOURCE_OPTIONS },
|
||||
{ label: '负责人', value: fPerson, set: setFPerson, opts: PERSON_OPTIONS },
|
||||
].map(f => (
|
||||
@ -413,6 +436,54 @@ export default function InquiryPage({ onLeadDetail }: { onLeadDetail?: (id: stri
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
<div ref={catRef} className="flex flex-col gap-1 relative">
|
||||
<span className="text-[10px] font-bold tracking-wider text-on-surface-variant uppercase">品类</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={catInput || (fCat === '全部品类' ? '' : fCat)}
|
||||
placeholder={fCat === '全部品类' ? '输入关键词搜索品类' : fCat}
|
||||
onFocus={() => setCatOpen(true)}
|
||||
onChange={e => { setCatInput(e.target.value); setCatOpen(true) }}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); setCatHighlightIdx(i => Math.min(filteredCatOptions.length - 1, i + 1)) }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); setCatHighlightIdx(i => Math.max(0, i - 1)) }
|
||||
else if (e.key === 'Enter') { e.preventDefault(); if (filteredCatOptions[catHighlightIdx]) selectCat(filteredCatOptions[catHighlightIdx]) }
|
||||
else if (e.key === 'Escape') { setCatOpen(false); setCatInput('') }
|
||||
else if (e.key === 'Backspace' && !catInput && fCat !== '全部品类') { e.preventDefault(); selectCat('全部品类') }
|
||||
}}
|
||||
className="bg-surface-container-low border border-border-subtle rounded pl-2 pr-6 py-1 text-xs w-32 focus:ring-secondary outline-none placeholder:text-slate-400"
|
||||
/>
|
||||
{fCat !== '全部品类' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => selectCat('全部品类')}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 w-4 h-4 flex items-center justify-center text-slate-400 hover:text-slate-600 hover:bg-slate-200 rounded-full text-[10px]"
|
||||
title="清空筛选"
|
||||
>×</button>
|
||||
)}
|
||||
</div>
|
||||
{catOpen && (
|
||||
<div className="absolute top-full left-0 mt-1 w-48 max-h-64 overflow-y-auto bg-white border border-border-subtle rounded-md shadow-lg z-30">
|
||||
{filteredCatOptions.length === 0 ? (
|
||||
<div className="px-3 py-2 text-[11px] text-slate-400">无匹配品类</div>
|
||||
) : (
|
||||
filteredCatOptions.map((c, idx) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onMouseEnter={() => setCatHighlightIdx(idx)}
|
||||
onClick={() => selectCat(c)}
|
||||
className={`w-full text-left px-3 py-1.5 text-xs flex items-center justify-between ${idx === catHighlightIdx ? 'bg-secondary text-white' : 'hover:bg-slate-50 text-slate-700'}`}
|
||||
>
|
||||
<span>{c}</span>
|
||||
{fCat === c && <span className={idx === catHighlightIdx ? 'text-white' : 'text-secondary'}>✓</span>}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
<span className="text-xs text-on-surface-variant">批量操作:</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user