From e8c036eb95e866e7ff125e5c5c645dca517df186 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Wed, 20 May 2026 21:03:51 +0800 Subject: [PATCH] auto-sync: 2026-05-20 21:03:51 --- docs/design/polling-ux-proposal.md | 103 +++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/design/polling-ux-proposal.md diff --git a/docs/design/polling-ux-proposal.md b/docs/design/polling-ux-proposal.md new file mode 100644 index 0000000..61b64e1 --- /dev/null +++ b/docs/design/polling-ux-proposal.md @@ -0,0 +1,103 @@ +# 轮询 UX 优化方案 + +> 日期:2026-05-20 +> 作者:庞统 +> 状态:待评审 +> 问题:5 秒全局轮询导致输入框失焦,用户无法在搜索框、新建对话框等输入文字 + +## 一、现状 + +### 当前轮询机制 +- `setInterval` 每 1 秒减 countdown,countdown 归零时 `loadAll()` +- `loadAll()` 每 5 秒执行:`loadLive()` → `loadProjects()` → `loadV2Tasks()` → 可选 `loadAgentConfig()` +- 右上角显示 `⟳ 5s` 倒计时 + +### 问题分析 +1. **输入失焦**:`set({ v2tasks: ... })` 触发 EdictBoard 重渲染,所有受控输入框失焦 +2. **请求浪费**:用户无操作时仍每 5 秒全量刷新 +3. **全部任务模式**:聚合 N 个项目的请求,5 秒一次压力大 +4. **视觉干扰**:倒计时数字不停跳动,分散注意力 + +## 二、方案:智能轮询 + 输入保护 + +### 2.1 输入焦点保护(核心修复) + +**原理**:轮询前检测是否有输入焦点,有则延迟刷新。 + +```typescript +// 判断是否有活跃输入 +function hasActiveInput(): boolean { + const el = document.activeElement; + if (!el) return false; + const tag = (el as HTMLElement).tagName; + return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' + || (el as HTMLElement).isContentEditable; +} +``` + +**轮询逻辑调整**: +- 有输入焦点 → 跳过本次 `loadV2Tasks()`,只刷新轻量数据(ticker) +- 输入结束 2 秒后补刷一次任务列表(用 `blur` 事件 + debounce) +- 倒计时继续走,不暂停计时器本身 + +### 2.2 轮询间隔分级 + +| 场景 | 间隔 | 说明 | +|------|------|------| +| 活跃操作后 | 10 秒 | 用户刚点击/切换 tab | +| 空闲状态 | 30 秒 | 无操作,静默刷新 | +| 全部任务模式 | 30 秒 | 聚合请求多,降低频率 | + +**实现**:记录上次用户操作时间戳,动态调整间隔。 + +### 2.3 视觉优化 + +| 改动 | 说明 | +|------|------| +| 去掉秒级倒计时 | 改为状态指示器:`●` 绿色=正常 `●` 黄色=刷新中 `●` 红色=连接失败 | +| 手动刷新按钮 | 点击立即刷新,重置计时器 | +| 刷新时卡片闪烁 | 用 CSS transition 平滑过渡,不要整块替换 | + +### 2.4 增量刷新(可选优化) + +当前每次轮询全量替换 `v2tasks` 数组。优化为: +- 比对新旧数据,只有状态变化时才更新对应任务 +- 减少不必要的重渲染 + +```typescript +// 简单增量合并 +const merged = newTasks.map(nt => { + const old = oldTasks.find(ot => ot.id === nt.id); + return (old && JSON.stringify(old) === JSON.stringify(nt)) ? old : nt; +}); +``` + +## 三、改动范围 + +| 文件 | 改动 | 行数估计 | +|------|------|---------| +| `store.ts` | 轮询逻辑 + hasActiveInput + 分级间隔 + 增量合并 | ~50 行 | +| `App.tsx` | 倒计时 UI → 状态指示器 + 手动刷新按钮 | ~20 行 | +| `EdictBoard.tsx` | 搜索框添加 onFocus/onBlur 事件(可选) | ~5 行 | + +**总计**:~75 行改动,不涉及后端。 + +## 四、不做的事 + +1. ❌ 不改 WebSocket(当前 HTTP 轮询够用,未来迁移成本不高) +2. ❌ 不改后端 API(轮询是前端优化) +3. ❌ 不做 Service Worker 缓存(过度设计) + +## 五、实施优先级 + +1. **P0**:输入焦点保护(解决失焦问题) +2. **P1**:轮询间隔 10→30 秒分级(减少请求) +3. **P2**:视觉优化(倒计时→状态指示器) +4. **P3**:增量刷新(性能优化,当前数据量不需要) + +## 六、验收标准 + +1. 在搜索框、新建项目对话框输入时,5 秒内不失焦 +2. 空闲 30 秒后自动刷新任务列表 +3. 手动刷新按钮可用 +4. 右上角不再有跳动数字