feat(record-detail): 记录详情页头部手动刷新按钮(原地刷新详情/相关/计数,不 reload 浏览器) - #3771
Merged
Conversation
Operator A sits on a record detail page while operator B writes to the same
record; A's only way to see it was F5, which discards the open tab, the scroll
position and any in-progress inline edit along with the stale data.
The objectui#2269 invalidation bus already refetches every mounted reader in
place, and `RecordContext.refresh` was already declared for exactly this — with
no producer and no consumer. Give it both:
- RecordDetailView produces `refresh`, publishing
`notifyDataChanged({ objectName: '*' })`. The wildcard is the honest scope: a
user clicks refresh because SOMEONE ELSE wrote data this client never saw, so
it cannot know which objects were touched. Standalone record route only —
embedded hosts (drawer, split-pane) are unchanged in this phase.
- page:header renders the button at the far end of the row when the host
provides `refresh`. Page chrome, not a header action: constant position, no
`maxVisible` overflow budget, no capability gate. Same outline pill as the
`...` overflow trigger so the row reads as one button family; accessible name
from the existing `common.refresh` key; spins for a floor of 650ms because the
bus is fire-and-forget.
- RelatedList accepts the `'*'` wildcard on the legacy
`objectui:related-changed` event, matching `dataChangeMatches`. A concrete
foreign object name is still ignored.
Hosts that provide no `refresh` render exactly as before.
Fixes #3460
…ord-header-refresh
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3460
背景
MES 派工单场景:A 操作员停留在详情页期间,B 操作员对同一单据做了开工/报工,A 只能靠 F5 才能看到最新状态 —— 而 F5 会把当前页签、滚动位置、正在编辑的行内草稿一起丢掉。
管道本来就齐:#2269 的失效总线已实现 "refresh data, don't rebuild UI",
RecordContext.refresh也早已声明 —— 声明了、消费方就位、类型与 lint 全绿,却既没有生产者也没有任何 UI 去读它(declared ≠ enforced 的教科书形状)。本 PR 给它补上两端。改动(3 个源文件 + 3 个测试文件 + 文档 + changeset)
packages/app-shell/src/views/RecordDetailView.tsx—— 生产者提供
RecordContext.refresh = () => notifyDataChanged({ objectName: '*' })。用'*'而非本记录是唯一诚实的作用域:用户点刷新的动机就是"别人改了我看不见的数据",这次写入本客户端从未见过,因此不可能知道动了哪些对象。'*'让主记录、每个相关子列表、页签计数徽标全部原地重拉。packages/components/src/renderers/layout/containers.tsx—— 消费者(page:header)宿主提供
refresh时,在头部操作区最右端渲染 ⟳(维护者裁决的位置)。样式取裁决的 B 案:与「⋯」溢出触发器同款 outline 胶囊(同 variant / 同 size / 同内边距),整排[开工][催办][编辑][⋯][⟳]读作一个按钮家族。它是页面 chrome 而非动作管线成员,这一点有测试钉住,不靠约定:位置恒定(不随对象声明的动作数变化)、不占
maxVisible预算因此永不会被折进「⋯」、不过requiredPermissions能力门控(重读一条已经在看的记录不是受限操作)。可访问名与 tooltip 复用现成的common.refresh(十语言均有,零 i18n 改动),避免图标按钮在另外九种语言下仍是英文(与 objectstack#5407 修掉的是同一类问题)。总线是 fire-and-forget、没有完成信号,所以图标至少转 650ms,否则热后端下点击看起来"什么都没发生"。packages/plugin-detail/src/RelatedList.tsx—— 一行旧事件
objectui:related-changed的监听器放行'*'通配,与dataChangeMatches语义对齐。此前它只比对自己的对象名,于是一次通配失效能到达页面上所有读者,唯独到不了相关列表 —— 这正是让 ⟳ 变成"半次刷新"的缺口。具体的外部对象名依旧被忽略(两个方向都钉了测试)。宿主不提供
refresh时,渲染结果与改动前完全一致。范围(按 2026-08-06 维护者裁决)
plugin-detail/src/DetailView.tsx一行未动)/ 自动刷新(WebSocket/轮询)挂起 ✅。DetailView,所以"不改 DetailView"自动等于"抽屉里没有 ⟳"。实读代码并非如此 ——ObjectView.tsx(分屏预览 + 抽屉两处)、ObjectDataPage.tsx、InterfaceListPage.tsx都是把RecordDetailView本体带embedded挂进覆盖层,渲染的是同一个page:header。若无条件提供refresh,被裁决排除的那些 surface 反而会长出按钮。因此生产者按embedded收口:refresh={embedded ? undefined : handleManualRefresh},与该视图既有的 embedded 抑制(自动"返回列表"、breadcrumb)同一套逻辑。这条有专门测试,并做了反向验证。验证
三包
type-check全过,lint0 error。新增 22 个测试,受影响三包全量:components+plugin-detail149 文件 1159 passed,app-shell296 文件 2645 passed(1 skipped,既有)。i18n 两个门(check:i18n-keys/check:i18n-drift)、check:control-bytes、check:changeset-*、新落地的check-changeset-presence全绿。反向验证(先预测方向,再跑) —— 四次拆卸,方向全部与预测一致:
'*'一例红,另 3 例绿page:header去掉hostRefresh门(无条件渲染)expected button to be null;expected 'DIV' to be 'HEADER')未做浏览器复跑,如实说明取舍:issue 正文的浏览器结论是提单人的;仓内现成的 #3391 harness(
apps/console/src/dev/DevRecordHeaderActions.tsx)自身是带embedded挂载的,不改它就照不到本路径,而改它超出本单声明的文件面。按钮的出现条件、位置、可访问名、旋转下限、回调、以及"点击后findOne再跑一次且 header DOM 节点不变(即原地刷新而非 remount)"都已由单测在真实渲染树上钉住,判断浏览器复跑不划算。Generated by Claude Code