✨ feat(vscode): 看板顶部加筛选框,按标题 / 工单号 / PR 号过滤卡片

工单多了以后靠肉眼在四列里翻太慢。顶部输入框按空白分词逐词 AND 匹配
标题、工单号、PR 号,`#427` 与 `427` 等价,纯数字按包含匹配,记不全
编号也能搜到。筛选只收窄渲染,拖拽与依赖锁定仍按全量列表算;列头角标
在筛选时显示「可见 / 总数」。
This commit is contained in:
cruldra committed 2026-09-20 17:24:54 +08:00
1 parent 73f2c3ec66
commit 9e255f517b
6 files changed
+167 -8

No files matched your search

+15 -2
View File
@@ -13,6 +13,7 @@ import { ToastStack } from './components/ToastStack'
import { useIssues } from './hooks/useIssues'
import { useManagedSessions } from './hooks/useManagedSessions'
import { useProfiles } from './hooks/useProfiles'
import { filterIssues } from './lib/issueFilter'
import { compareIssuesInColumn } from './lib/issueSort'
import { COLUMN_ORDER } from './types'
@@ -127,6 +128,9 @@ export function App() {
setShowNewIssueModal(false)
}
// 顶部筛选框内容。纯前端过滤,不影响拖拽与详情面板拿到的全量列表。
const [filterQuery, setFilterQuery] = useState('')
// Drop selectedId if it's no longer present after issues refresh.
const readyIssues = state.status === 'ready' ? state.issues : null
useEffect(() => {
@@ -153,13 +157,15 @@ export function App() {
// Issues partitioned by column, in COLUMN_ORDER. Used for arrow navigation.
// 排序与看板视觉一致:完成列按 PR 合并时间降序,其余列按工单号降序。
// 键盘导航只在筛选后可见的卡片之间走,和看板显示保持一致。
const ordered = useMemo<Issue[][]>(() => {
if (!readyIssues)
return []
const visible = filterIssues(readyIssues, filterQuery)
return COLUMN_ORDER.map(col =>
readyIssues.filter(i => i.column === col).sort((a, b) => compareIssuesInColumn(col, a, b)),
visible.filter(i => i.column === col).sort((a, b) => compareIssuesInColumn(col, a, b)),
)
}, [readyIssues])
}, [readyIssues, filterQuery])
// Resolved selected issue (or null) for the detail panel.
const selectedIssue = useMemo<Issue | null>(() => {
@@ -312,6 +318,8 @@ export function App() {
{state.status === 'loading' && (
<>
<PanelHeader
filterQuery={filterQuery}
onFilterChange={setFilterQuery}
onRefresh={refresh}
refreshing={refreshing}
scope={scope}
@@ -339,6 +347,8 @@ export function App() {
{state.status === 'ready' && (
<>
<PanelHeader
filterQuery={filterQuery}
onFilterChange={setFilterQuery}
onRefresh={refresh}
refreshing={refreshing}
scope={scope}
@@ -361,6 +371,7 @@ export function App() {
<div className="min-h-0 flex-1 overflow-hidden">
<KanbanBoard
issues={state.issues}
filterQuery={filterQuery}
onIssuesChange={setIssues}
onCreateIssue={() => setShowNewIssueModal(true)}
selectedId={selectedId}
@@ -445,6 +456,8 @@ export function App() {
{state.status === 'error' && (
<>
<PanelHeader
filterQuery={filterQuery}
onFilterChange={setFilterQuery}
onRefresh={refresh}
refreshing={refreshing}
scope={scope}