feat(console): 侧栏项目按优先级显示三类计数徽标

左侧项目状态新增数字提示,按优先级展示:
1. 需人工处理数(紫,attention)
2. agent 运行数(青,executing)
3. 被阻塞数(琥珀,blocked)
仅非零显示,固定「人工 > 运行 > 阻塞」排列;折叠态仍单状态点,
任一计数 > 0 时放大。后端 projectSummary 已就绪,仅透出 blocked
并渲染。i18n 五语言补齐 projAgentsTip / projBlockedTip。

tsk_0VF1URfdoe3d

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 07:34:47 +08:00
parent db4e6e2fb4
commit 33bef7f5fc
4 changed files with 34 additions and 14 deletions
+20 -10
View File
@@ -48,6 +48,17 @@ function projStateMeta(state, t) {
})[state] || { color: 'var(--muted)', pulse: false, label: t.projIdle };
}
// 项目计数徽标:按优先级展示「需人工 / 运行中 / 被阻塞」,仅非零显示
function CountBadge({ value, color, title }) {
return (
<span title={title} style={{
fontFamily: 'var(--mono)', fontSize: 9.5, fontWeight: 700, lineHeight: '15px',
minWidth: 15, height: 15, textAlign: 'center', padding: '0 4px',
color: 'var(--bg-deep)', background: color, borderRadius: 8,
}}>{value}</span>
);
}
function ProjStatusDot({ meta, size = 7 }) {
return (
<span style={{
@@ -509,7 +520,10 @@ function Sidebar({ projects, currentId, onSelect, onNewProject, collapsed, onTog
const active = p.id === currentId;
const meta = projStateMeta(p.state, t);
return (
<li key={p.id} onClick={() => onSelect(p.id)} title={p.name + ' · ' + meta.label + (p.pending ? ' · ' + t.projPendingTip.replace('{n}', p.pending) : '')}
<li key={p.id} onClick={() => onSelect(p.id)} title={p.name + ' · ' + meta.label
+ (p.pending ? ' · ' + t.projPendingTip.replace('{n}', p.pending) : '')
+ (p.agents ? ' · ' + t.projAgentsTip.replace('{n}', p.agents) : '')
+ (p.blocked ? ' · ' + t.projBlockedTip.replace('{n}', p.blocked) : '')}
draggable={!collapsed}
onDragStart={(e) => { setDragId(p.id); e.dataTransfer.effectAllowed = 'move'; }}
onDragOver={(e) => { if (dragId) { e.preventDefault(); if (p.id !== dragId && overId !== p.id) setOverId(p.id); } }}
@@ -536,20 +550,16 @@ function Sidebar({ projects, currentId, onSelect, onNewProject, collapsed, onTog
<span style={{ fontWeight: 600, fontSize: 13, color: active ? 'var(--green)' : 'var(--ink)' }}>{p.name}</span>
<span style={{ fontSize: 10.5, color: 'var(--faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.path}</span>
</span>
<span style={{ flex: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
{p.pending > 0 ? (
<span title={t.projPendingTip.replace('{n}', p.pending)} style={{
fontFamily: 'var(--mono)', fontSize: 9.5, fontWeight: 700, lineHeight: '15px',
minWidth: 15, height: 15, textAlign: 'center', padding: '0 4px',
color: 'var(--bg-deep)', background: 'var(--violet)', borderRadius: 8,
}}>{p.pending}</span>
) : null}
<span style={{ flex: 'none', display: 'flex', alignItems: 'center', gap: 5 }}>
{p.pending > 0 ? <CountBadge value={p.pending} color="var(--violet)" title={t.projPendingTip.replace('{n}', p.pending)} /> : null}
{p.agents > 0 ? <CountBadge value={p.agents} color="var(--cyan)" title={t.projAgentsTip.replace('{n}', p.agents)} /> : null}
{p.blocked > 0 ? <CountBadge value={p.blocked} color="var(--amber)" title={t.projBlockedTip.replace('{n}', p.blocked)} /> : null}
<ProjStatusDot meta={meta} />
</span>
</React.Fragment>
) : (
<span style={{ position: 'absolute', top: 7, right: 9, display: 'flex', alignItems: 'center' }}>
<ProjStatusDot meta={meta} size={p.pending > 0 ? 8 : 6} />
<ProjStatusDot meta={meta} size={(p.pending || p.agents || p.blocked) > 0 ? 8 : 6} />
</span>
)}
</li>