Two pieces of upstream promotion came out of this deployment. The sidebar "apiRelay" tab only opened a registration page on a third-party site, so it goes with no replacement. The circuit badge next to Settings was not what it looked like. It opened an ESP32 device manager: add a device, show LAN status, remote connect/disconnect, rename, delete, backed by six REST routes and an mcu_devices table. The purchase button inside that dialog was the promotional part; the rest was real device management. Since the hardware is out of scope here, the entire feature is removed rather than just the link that pointed upstream - UI entry, client API, routes, controller, store, schema, locale strings, and the one GlobalAgentServer method that only existed to answer its status column. Table creation is dropped but no DROP TABLE was added, so an already-provisioned database keeps its rows as harmless orphans instead of losing data on upgrade. DevicesView and the hermes.devices route are untouched: that is LAN device discovery, a separate feature. AGENTS.md now records the removal so the badge does not come back.
11 KiB
11 KiB
已知问题与迁移遗留
记录 UI 库迁移(Phase 006-007,naive-ui → Arco Design Vue)过程中发现但按规则未修的问题,以及自动化测试测不到、只能靠手工验证的风险点。
用法:日常使用时遇到异常表现,先来本文档搜关键词。确认命中后按「证据」栏定位,确认是同一个问题再修。
新增条目请追加到对应分区末尾,不要重排已有条目。
一、已确认的业务逻辑缺陷
这些是读代码时确认存在的真实缺陷,与迁移无关(迁移前就存在),但迁移过程中被注意到。修它们属于逻辑改动,需单独开分支。
1. ModelSelector 的 computed 里有副作用
- 现象:切换模型时 provider 选择偶尔「自己跳」,或
customProvider状态被莫名改写 - 位置:
packages/client/src/components/layout/ModelSelector.vue,providerOptionscomputed 的 getter - 证据:computed 的 getter 内部给
customProvider.value赋值。computed 应当是纯读。副作用是:任何其它 computed 或渲染逻辑读到providerOptions,都会顺带改写customProvider - 修法:把赋值移到
watch或显式的 setter 里
2. ProfileSelector 把 i18n key 当文案用
- 现象:某些运行时状态提示里显示原始 key(如
profiles.runtime.checking)而不是中文/英文文案 - 位置:
packages/client/src/components/layout/ProfileSelector.vue,gatewayStatusText/bridgeStatusText - 证据:
message.warning(t('profiles.runtime.checking'))——t()的结果(一个 key 字符串)被直接当消息正文,缺了外层再套一次t() - 修法:改成
message.warning(t('profiles.runtime.checking'))(让t()返回字符串函数的结果),即检查是否需要t(t(key))或改用te()+ 手动查表
3. McpManagerView 的 _autoRetryCount 赋值可能静默失效
- 现象:MCP 失败后点「重试」,自动重试计数似乎没有正确归零
- 位置:
packages/client/src/views/hermes/McpManagerView.vue,let _autoRetryCount(模块级) - 证据:在 header 按钮的内联表达式
@click="_autoRetryCount = 0; loadServers()"里被重置。<script setup>中的let绑定在模板内联语句里不可写(编译器只对 ref 做解包),这个重置很可能不生效 - 修法:改成
ref,或把重置逻辑移进一个具名函数
4. McpManagerView 的 handleModeChange 有冗余重试
- 现象:无功能影响,仅代码冗余
- 位置:
packages/client/src/views/hermes/McpManagerView.vue,handleModeChange - 证据:两段重复的 try/catch 用同一个
mode解析同一份文本,第一次失败后用相同模式重试一次才 return。第一个 catch 分支应直接 return
5. WorkflowView 的批量删除确认 ref 已无读取方
- 现象:无功能影响
- 位置:
packages/client/src/views/hermes/WorkflowView.vue,showWorkflowBatchDeleteConfirm - 证据:naive 的
NPopconfirm v-model:show需要它;Arco 的a-popconfirm无showprop 后它失去读取方,只剩写。关闭改由@ok/@cancel驱动
6. ProfileCreateModal 有两个重复的 style 块
- 现象:无功能影响
- 位置:
packages/client/src/components/hermes/profiles/ProfileCreateModal.vue - 证据:两个
<style scoped>内容相同(都是.modal-footer)。迁移前存在
二、Arco 与 naive 的行为差异(可能表现为「功能坏了」)
迁移时按等价语义做了处理,但以下差异在某些边界条件下可能暴露。如遇异常,先查这里。
a-input-password 默认是遮蔽的
- 风险:任何给
a-input-password传:default-visibility="false"的地方,密码/API Key 会明文显示 - 正确做法:不要传这个 prop。源码
es/input/input-password.js:77是type: mergedVisible ? "password" : "text",defaultVisibility默认true即遮蔽 - 检查命令:
grep -rn "default-visibility\|defaultVisibility" packages/client/src(期望 0 命中)
a-upload 默认会发真实 XHR
- 风险:没加
:auto-upload="false"的上传组件,选一次文件就向当前页 URL 发一个无意义 multipart 请求 - 正确做法:凡是「只选文件、由 store 走 API」的上传,
a-upload必须显式加:auto-upload="false" - 涉及文件:
ProfileImportModal.vue、SkillImportModal.vue、FileUploadModal.vue
Arco Trigger 不能嵌套
- 风险:
a-tooltip/a-dropdown/a-popconfirm/a-popover互相嵌套时,菜单或提示点不开 - 原因:它们内部都用
mergeFirstChild接管首个元素节点的事件,多层会互相踩 - 正确做法:同一元素既要 tooltip 又要 dropdown 时,tooltip 改用原生
:title - 已处理:
GroupChatInput.vue的设置按钮(NDropdown > NTooltip > NButton→a-dropdown > a-button+:title)、ChatInput.vueL1130-1162
tooltip/dropdown 默认插槽必须只含一个元素
- 风险:尾随的纯文本(
{{ t(...) }})会被当可见内容渲染进布局流,变成 0 宽的 flex item,把工具栏挤成竖排或让文本被截断 - 正确做法:文案放
contentprop,默认插槽只留触发元素 - 已踩坑位置:
ChatInput.vue三处、GroupChatInput.vue、TerminalPanel.vue、KanbanTaskCard.vue、KanbanView.vue排序按钮 - 自查:
grep -rn "a-tooltip" -A 8 packages/client/src | grep -B2 "{{ t"然后肉眼确认
NDropdown 的 droplist 没有对应
- 风险:给
a-dropdown传droplist会静默失效(Arco 不对未声明的 prop 报错,未声明的 prop 落为普通 attribute) - 正确做法:菜单写进
#content插槽用<a-doption> - 历史:Phase 006 的三个下拉全打不开,根因就是这个
a-input / a-textarea 的 modelValue 不接受 null
- 风险:
null初值会导致该组件显示异常或 TS 报错 - 正确做法:
:model-value="x ?? undefined",配合@change="x = $event ?? null"桥接保住 null 语义 - 涉及:
AuxiliaryModelsPanel.vue、DisplaySettings.vue、VoiceApiFormModal.vue、KanbanCreateForm.vue、JobFormModal.vue、GroupChatPanel.vue
a-spin 的 size 是 Number(px)不是字符串档位
- 风险:传
size="small"会被拼成fontSize: "smallpx" - 正确做法:传
:size="14"这样的数字 - 涉及:
PlatformSettings.vue、ProfileCard.vue、JobRunHistory.vue、VoiceApiFormModal.vue
a-input-password 没有 width prop
- 风险:
:width="220"被静默丢弃(底层 Trigger 设了inheritAttrs: false) - 正确做法:改用
:content-style
a-scrollbar 用 max-height 会高度塌陷
- 正确做法:必须显式给
height - 涉及:
McpManagerView.vue
a-modal 的 footer 默认 true
- 风险:原本没有 footer 插槽的弹窗,会多出取消/确定按钮
- 正确做法:无自定义 footer 插槽时加
:footer="false";有插槽时绝对不能加(加了插槽内容不渲染)
Arco 按钮在 loading 时吞掉后续 click
- 风险:快速连点同一按钮,第二次点击不触发
- 影响:
SkillsUsageView.vue的刷新按钮(测试已改写场景绕过,但 UI 上真实存在) - 备注:不是 bug,是 Arco 的行为
三、自动化测不到、需手工确认的风险点
单元测试和 e2e 都覆盖不到这些。首次正式使用时请重点走一遍。
右键菜单
- 文件:
FileContextMenu.vue、WorkflowView.vue、HistoryView.vue、GroupChatPanel.vue - 风险:naive 的
NDropdown trigger="manual"+:x/:y迁到了a-trigger trigger="contextMenu"+:popup-style - 需确认:① 右键能弹出 ② 菜单打开时滚动页面,菜单是否跟随滚动(Arco 在
autoFitPosition: false时不重算位置,菜单会留在原地) - 需确认:③ 刚右键弹出就立刻点别处,菜单是否被误关(naive 靠
contextMenuOpenedAt时间戳防抖,Arco 靠内置clickOutsideToClose,时序可能微妙不同)
文件树懒加载
- 文件:
FileTree.vue(naiveNTree→ Arcoa-tree) - 需确认:① 点目录能加载子目录 ② 只加载目录不加载文件(
filter(e => e.isDir))③ 快速切换目录时不会出现子节点串台(深层懒加载的竞态保护弱于根节点,rootLoadSeq只在切 profile/session 时自增)
Kanban 折叠与布局
- 文件:
KanbanView.vue、KanbanColumn.vue - 备注:迁移时发现 kanban 实际没有拖拽逻辑(排序靠卡片上的 sort 按钮),任务书里的「拖拽落点」风险不存在
- 需确认:① 折叠/展开的列状态正确 ② Arco
a-collapse默认全部展开(已显式设:default-expand-all="false")
表格列的省略与 tooltip
- 文件:
UserManagementSettings.vue - 备注:naive 的
ellipsis: { tooltip: true }→ Arco 的ellipsis: true+ 列顶层tooltip: true - 需确认:长文本省略后悬浮能显示完整内容
视觉细节
- 圆角 tag:naive 的
NTag round无 Arco 对应,改用了border-radius: 999px(PetdexView.vue、DevicesView.vue) - 按钮尺寸:naive 的
size="tiny"→ Arcosize="mini",可能与相邻元素不齐 - 滚动条:naive 的
:native-scrollbar="false"无对应,已删除,滚动条样式会变
弹窗的遮罩/ESC 行为
- 风险:Arco 默认
maskClosable和escToClose都是true。用户填了一半的表单按 ESC 或点遮罩会直接丢弃且不重置 - 涉及:
VoiceApiFormModal.vue、VoiceApiConfigurator.vue - 需确认:是否应该设
:mask-closable="false"/:esc-to-close="false"
模型选择弹窗(ProviderFormModal)
- 备注:naive 的
NButton type="primary" secondary(浅色主按钮)在 Arco 无对应组合,去掉了secondary保留type="primary",视觉变成实心主色按钮 - 需确认:视觉是否可接受
四、其它观察
TerminalView.vue的终端主题下拉选项 label 是硬编码英文("Default"、"Monokai"),未走 i18n。迁移前既有问题AuxiliaryModelsPanel.vue有 5 处手写<label>+<span>而非a-form-item,与其它 Arco 表单的对齐/必填星号规则不一致ProviderFormModal.vue的handleSave有 6 个近乎重复的 OAuth 分支,重复度高、容易漏改,建议后续合并成 provider→trigger 映射表FileUploadModal.vue关闭时只清空了本地fileList,未清a-upload内部状态。目前靠 modal 默认unmount-on-close兜底,若有人关掉该 prop 会出现「列表已清空但 UI 仍显示」McpManagerView.vue的handleModeChange与FileContextMenu.vue的show()快速连点时,Arco popup 的 transition 理论上可能动画排队
相关文档
docs/arco-migration-rules.md—— 迁移规约与完整组件映射表docs/arco-mapping.md—— naive → Arco 映射与结构差异docs/chat-chain-changes/2026-10-18-phase007-arco-full-migration.md—— Phase 007 变更清单