OpsMind UI 重构实录(一):先把上传链路和宽表配置打通
OpsMind UI 重构实录(一):先把上传链路和宽表配置打通
wwxdsg这次 UI 重构我最后一共做了三天。现在回头看,第一天其实没什么“高级设计感”,更多是在做一件很工程化、但也很必要的事:
先把主链路打通。
因为如果上传文件就 500,宽表识别不出来,前端也没有地方承接用户的配置,那后面所有关于质感、主题、排版的优化都会显得有点奢侈。
所以第一天的重点,不是把界面做漂亮,而是把“上传一个表,然后让系统真的知道该怎么处理它”这条链路做完整。
先修一个很不体面的 500
最先碰到的问题其实挺朴素:用户上传带年份列的 CSV,比如 2009、2010 这种表头,后端直接返回 500,而且前端拿不到有用信息。
这种问题最烦的地方不只是报错,而是报得很没礼貌。
你知道它错了,但它不打算告诉你为什么。
我做的第一件事,是在 src/api/main.py 里临时加一个全局异常处理器,把完整 traceback 透出来。开发期这一步很值,因为它能把“纯猜”变成“可定位”。
很快就发现根因不在上传本身,而在返回 preview 的时候:
df.to_dict(orient="records") |
当 pandas 读到年份列时,列名会是整数;但 Pydantic 这边定义的是 Dict[str, Any],它只接受字符串键。于是本来只是个预览数据,最后被整数 key 拖进了 ValidationError。
修法很简单:
preview_df.columns = preview_df.columns.astype(str) |
这类问题特别适合记进脑子里,因为它不是业务逻辑错了,而是两个正常库在接口边界上没对齐。
以后凡是 DataFrame.to_dict() 后面还要进 Pydantic,我基本都会先想到这一层。
上传不是终点,结构识别才是
把 500 修掉之后,事情才刚刚开始。
因为这次改动的真正目标,不是“能上传文件”,而是让系统在看到一张宽表的时候,知道它需要额外处理。
所以后端又补了一层 StructureInfo,专门描述这张表的结构特征:
- 是否需要人工注意
- 它更像
wide_year、wide_date还是普通表 - 哪些列像年份列
- 哪些列像文本维度
- 有没有空列、匿名列
- 以及一份建议配置
这一步我很喜欢,因为它让上传接口从“单纯接收文件”升级成了“对数据做第一轮判断”。
也就是说,文件一传上来,系统不只是说“我收到了”,而是在说:
我大概知道这张表长什么样了,接下来你可能需要决定怎么展开它。
这就是体验的分水岭。
前一种更像存储接口,后一种才开始有点分析产品的味道。
宽表转换这件事,最好让用户先看见
光有结构检测还不够。真正让我觉得这条链路“活了”的,是前端那块配置面板。
这次做法不是把所有转换逻辑都扔给后端,而是前端先自己做一份 meltPreview(),直接在本地把表预演一遍。
function meltPreview( |
这样做的好处非常直接:
- 用户点列名切换角色时,预览能立刻变化
- 不用每改一次配置就打一次后端
- “我到底在把这张表变成什么”这件事变得可见了
我后来越来越喜欢这种设计:
后端负责给出判断和持久化,前端负责把决策过程做得即时、透明、低摩擦。
列角色系统,是这一天最像产品设计的一笔
为了让配置过程尽量轻,我没有做一堆复杂表单,而是给每一列定义了三个角色:
dimmetricexclude
点击一次就循环切换:
function nextRole(r: ColRole): ColRole { |
这其实是个很小的交互,但我很喜欢。因为它把原本容易做得很重的一件事,压缩成了一个非常直接的操作模型:
- 这是维度
- 这是指标
- 这个不要
没有额外表单,没有复杂弹窗,没有让人读半天的设置项。
用户只需要一边点,一边看预览怎么变。
这种感觉很像:系统不是在逼你“填写配置”,而是在和你一起把表格整理成它能理解的形状。
最后再把配置落回 session
等用户确认完之后,前端再调:
PATCH /api/sessions/{session_id}/table-config |
把这份 table_config 存进 session metadata。之后每次工作流执行前,再由后端把这个配置注入给 data_engine。
这一点也很关键。因为它意味着配置不是一次性的 UI 状态,而是会话语义的一部分。
也就是说,系统会记得:
- 这张表原来是宽表
- 用户上次决定怎么展开它
- 后续分析都应该沿用这份理解
这就比“用户每次上传都重新解释一遍”自然得多。
第一天做完后,我真正放心的不是页面,而是链路
如果只看视觉,这一天其实还谈不上惊艳。
但它完成了一件更底层的事:把上传、识别、预览、确认、持久化这条线串起来了。
回头想,这一天的意义有点像在搭地基:
- 修掉一个不体面的 500
- 让后端对表结构有判断能力
- 让前端把转换过程可视化
- 让最终配置进入 session,而不是停留在瞬时状态
后面两天能开始认真谈设计 token、双主题、消息区排版、Markdown 渲染器这些“我很喜欢”的内容,某种意义上也是因为第一天先把产品最核心的路径扶正了。
所以如果要给这一天下个定义,我会说:
它不是最漂亮的一天,但它把整个 UI 重构从“样式升级”拉回了“产品真正变完整”。