3.6 KiB
3.6 KiB
轮询 UX 优化方案
日期:2026-05-20 作者:庞统 状态:待评审 问题:5 秒全局轮询导致输入框失焦,用户无法在搜索框、新建对话框等输入文字
一、现状
当前轮询机制
setInterval每 1 秒减 countdown,countdown 归零时loadAll()loadAll()每 5 秒执行:loadLive()→loadProjects()→loadV2Tasks()→ 可选loadAgentConfig()- 右上角显示
⟳ 5s倒计时
问题分析
- 输入失焦:
set({ v2tasks: ... })触发 EdictBoard 重渲染,所有受控输入框失焦 - 请求浪费:用户无操作时仍每 5 秒全量刷新
- 全部任务模式:聚合 N 个项目的请求,5 秒一次压力大
- 视觉干扰:倒计时数字不停跳动,分散注意力
二、方案:智能轮询 + 输入保护
2.1 输入焦点保护(核心修复)
原理:轮询前检测是否有输入焦点,有则延迟刷新。
// 判断是否有活跃输入
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 数组。优化为:
- 比对新旧数据,只有状态变化时才更新对应任务
- 减少不必要的重渲染
// 简单增量合并
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 行改动,不涉及后端。
四、不做的事
- ❌ 不改 WebSocket(当前 HTTP 轮询够用,未来迁移成本不高)
- ❌ 不改后端 API(轮询是前端优化)
- ❌ 不做 Service Worker 缓存(过度设计)
五、实施优先级
- P0:输入焦点保护(解决失焦问题)
- P1:轮询间隔 10→30 秒分级(减少请求)
- P2:视觉优化(倒计时→状态指示器)
- P3:增量刷新(性能优化,当前数据量不需要)
六、验收标准
- 在搜索框、新建项目对话框输入时,5 秒内不失焦
- 空闲 30 秒后自动刷新任务列表
- 手动刷新按钮可用
- 右上角不再有跳动数字