From a822d7782b335c8e68bc8a45e48668c6336a4729 Mon Sep 17 00:00:00 2001 From: Sisyphus Date: Wed, 1 Jul 2026 14:18:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E5=93=81=E7=B1=BB=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E6=A1=86=E5=8D=87=E7=BA=A7=E4=B8=BA=E5=8F=AF=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=85=B3=E9=94=AE=E8=AF=8D=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=20Combobox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【问题】 询价发布助手筛选工具栏的品类下拉框有 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 --- apps/web/src/components/InquiryPage.tsx | 75 ++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/InquiryPage.tsx b/apps/web/src/components/InquiryPage.tsx index eca3a70..3c41193 100644 --- a/apps/web/src/components/InquiryPage.tsx +++ b/apps/web/src/components/InquiryPage.tsx @@ -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(['全部品类']) + const [catInput, setCatInput] = useState('') + const [catOpen, setCatOpen] = useState(false) + const [catHighlightIdx, setCatHighlightIdx] = useState(0) + const catRef = useRef(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 ))} +
+ 品类 +
+ 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 !== '全部品类' && ( + + )} +
+ {catOpen && ( +
+ {filteredCatOptions.length === 0 ? ( +
无匹配品类
+ ) : ( + filteredCatOptions.map((c, idx) => ( + + )) + )} +
+ )} +
批量操作: