跳转到内容

Log 日志 ​

等宽排版的滚动区域,一行一条,可以自动跟随到底部。

用法 ​

root / viewport / content / line 四层;一行写什么由作者决定,组件只提供身份与等宽排版

12:00:01 boot 读取配置 config/app.yaml
12:00:01 boot 监听 0.0.0.0:8080
12:00:02 db 连接池就绪,最小 4 最大 32
12:00:02 cache 命中率统计已开启
12:00:03 http GET /health 200 3ms
12:00:04 http POST /api/orders 201 118ms
12:00:05 http GET /api/orders/8812 200 21ms
12:00:06 job 对账任务排入队列 batch-2026-08-10
12:00:07 http GET /api/orders/8813 404 9ms
12:00:08 job 对账任务完成,处理 1,204 笔

组件结构 ​

加粗的是必需部件。

data-scope="log":root · viewport · content · line · scroll-to-end-trigger · live-region

示例 ​

按行数定高 ​

rows 决定可见几行,一行的高度归皮肤,修改 --xh-log-line-height 两者一起变化

12:00:00 http GET /api/items/1000 200
12:00:01 http GET /api/items/1001 200
12:00:02 http GET /api/items/1002 200
12:00:03 http GET /api/items/1003 200
12:00:04 http GET /api/items/1004 200
12:00:05 http GET /api/items/1005 200
12:00:06 http GET /api/items/1006 200
12:00:07 http GET /api/items/1007 200
12:00:08 http GET /api/items/1008 200
12:00:09 http GET /api/items/1009 200
12:01:00 http GET /api/items/1010 200
12:01:01 http GET /api/items/1011 200
12:01:02 http GET /api/items/1012 200
12:01:03 http GET /api/items/1013 200
12:01:04 http GET /api/items/1014 200
12:01:05 http GET /api/items/1015 200
12:01:06 http GET /api/items/1016 200
12:01:07 http GET /api/items/1017 200
12:01:08 http GET /api/items/1018 200
12:01:09 http GET /api/items/1019 200
12:02:00 http GET /api/items/1020 200
12:02:01 http GET /api/items/1021 200
12:02:02 http GET /api/items/1022 200
12:02:03 http GET /api/items/1023 200
12:00:00 http GET /api/items/1000 200
12:00:01 http GET /api/items/1001 200
12:00:02 http GET /api/items/1002 200
12:00:03 http GET /api/items/1003 200
12:00:04 http GET /api/items/1004 200
12:00:05 http GET /api/items/1005 200
12:00:06 http GET /api/items/1006 200
12:00:07 http GET /api/items/1007 200
12:00:08 http GET /api/items/1008 200
12:00:09 http GET /api/items/1009 200
12:01:00 http GET /api/items/1010 200
12:01:01 http GET /api/items/1011 200
12:01:02 http GET /api/items/1012 200
12:01:03 http GET /api/items/1013 200
12:01:04 http GET /api/items/1014 200
12:01:05 http GET /api/items/1015 200
12:01:06 http GET /api/items/1016 200
12:01:07 http GET /api/items/1017 200
12:01:08 http GET /api/items/1018 200
12:01:09 http GET /api/items/1019 200
12:02:00 http GET /api/items/1020 200
12:02:01 http GET /api/items/1021 200
12:02:02 http GET /api/items/1022 200
12:02:03 http GET /api/items/1023 200
12:00:00 http GET /api/items/1000 200
12:00:01 http GET /api/items/1001 200
12:00:02 http GET /api/items/1002 200
12:00:03 http GET /api/items/1003 200
12:00:04 http GET /api/items/1004 200
12:00:05 http GET /api/items/1005 200
12:00:06 http GET /api/items/1006 200
12:00:07 http GET /api/items/1007 200
12:00:08 http GET /api/items/1008 200
12:00:09 http GET /api/items/1009 200
12:01:00 http GET /api/items/1010 200
12:01:01 http GET /api/items/1011 200
12:01:02 http GET /api/items/1012 200
12:01:03 http GET /api/items/1013 200
12:01:04 http GET /api/items/1014 200
12:01:05 http GET /api/items/1015 200
12:01:06 http GET /api/items/1016 200
12:01:07 http GET /api/items/1017 200
12:01:08 http GET /api/items/1018 200
12:01:09 http GET /api/items/1019 200
12:02:00 http GET /api/items/1020 200
12:02:01 http GET /api/items/1021 200
12:02:02 http GET /api/items/1022 200
12:02:03 http GET /api/items/1023 200

自动跟随到底部 ​

新行进入时视口自动跟随;向上滚动一段即停止跟随,组件报告的 atBottom 与 scrollToBottom 足以自行绘制一条回到最新

12:00:00 boot 第 1 行 · 往上滚一段试试
12:00:01 boot 第 2 行 · 往上滚一段试试
12:00:02 boot 第 3 行 · 往上滚一段试试
12:00:03 boot 第 4 行 · 往上滚一段试试
12:00:04 boot 第 5 行 · 往上滚一段试试
12:00:05 boot 第 6 行 · 往上滚一段试试
12:00:06 boot 第 7 行 · 往上滚一段试试
12:00:07 boot 第 8 行 · 往上滚一段试试
12:00:08 boot 第 9 行 · 往上滚一段试试
12:00:09 boot 第 10 行 · 往上滚一段试试

取行中 ​

loading 使日志区报告 aria-busy 并把指针换为忙碌态;正在拉取那一行由作者自行渲染

12:00:01 boot 服务已启动
12:00:02 db 连接池就绪
12:00:03 http GET /health 200

级别 ​

行上写 level,四档 debug / info / warn / error 由皮肤染色;时间戳与行内标记仍归作者

12:00:01 [DEBUG] 读取配置 config/app.yaml
12:00:02 [INFO] 数据库连接池就绪
12:00:04 [INFO] POST /api/orders 201 118ms
12:00:05 [WARN] 慢查询 1,240ms select * from orders
12:00:06 [ERROR] 支付网关超时,第 1 次重试
12:00:08 [INFO] 支付网关恢复,订单 8812 已确认

换为自绘滚动条 ​

视口提供一个 id,用滚动条的 controls 挂载;滚动条浮在内容之上,不占宽度也不留空道

12:00:00 http GET /api/orders/8800 200 10ms
12:00:01 http GET /api/orders/8801 200 11ms
12:00:02 http GET /api/orders/8802 200 12ms
12:00:03 http GET /api/orders/8803 200 13ms
12:00:04 http GET /api/orders/8804 200 14ms
12:00:05 http GET /api/orders/8805 200 15ms
12:00:06 http GET /api/orders/8806 200 16ms
12:00:07 http GET /api/orders/8807 200 17ms
12:00:08 http GET /api/orders/8808 200 18ms
12:00:09 http GET /api/orders/8809 200 19ms
12:01:10 http GET /api/orders/8810 200 20ms
12:01:11 http GET /api/orders/8811 200 21ms
12:01:12 http GET /api/orders/8812 200 22ms
12:01:13 http GET /api/orders/8813 200 23ms
12:01:14 http GET /api/orders/8814 200 24ms
12:01:15 http GET /api/orders/8815 200 25ms
12:01:16 http GET /api/orders/8816 200 26ms
12:01:17 http GET /api/orders/8817 200 27ms
12:01:18 http GET /api/orders/8818 200 28ms
12:01:19 http GET /api/orders/8819 200 29ms
12:02:20 http GET /api/orders/8820 200 30ms
12:02:21 http GET /api/orders/8821 200 31ms
12:02:22 http GET /api/orders/8822 200 32ms
12:02:23 http GET /api/orders/8823 200 33ms
12:02:24 http GET /api/orders/8824 200 34ms
12:02:25 http GET /api/orders/8825 200 35ms
12:02:26 http GET /api/orders/8826 200 36ms
12:02:27 http GET /api/orders/8827 200 37ms
12:02:28 http GET /api/orders/8828 200 38ms
12:02:29 http GET /api/orders/8829 200 39ms
12:03:30 http GET /api/orders/8830 200 40ms
12:03:31 http GET /api/orders/8831 200 41ms
12:03:32 http GET /api/orders/8832 200 42ms
12:03:33 http GET /api/orders/8833 200 43ms
12:03:34 http GET /api/orders/8834 200 44ms
12:03:35 http GET /api/orders/8835 200 45ms
12:03:36 http GET /api/orders/8836 200 46ms
12:03:37 http GET /api/orders/8837 200 47ms
12:03:38 http GET /api/orders/8838 200 48ms
12:03:39 http GET /api/orders/8839 200 49ms

回到底部与播报 ​

向上翻一段,右下角的按钮自动显示,按下后归位并重新粘附;输出结束后在播报区朗读一句结论

12:00:00 build 编译 packages/module-1 · 往上翻一段试试
12:00:01 build 编译 packages/module-2 · 往上翻一段试试
12:00:02 build 编译 packages/module-3 · 往上翻一段试试
12:00:03 build 编译 packages/module-4 · 往上翻一段试试
12:00:04 build 编译 packages/module-5 · 往上翻一段试试
12:00:05 build 编译 packages/module-6 · 往上翻一段试试
12:00:06 build 编译 packages/module-7 · 往上翻一段试试
12:00:07 build 编译 packages/module-8 · 往上翻一段试试
12:00:08 build 编译 packages/module-9 · 往上翻一段试试
12:00:09 build 编译 packages/module-10 · 往上翻一段试试

设计指引 ​

何时使用 ​

  • 构建输出、运行日志、命令行回显。
  • 任何从底部持续增长、需要始终跟随到底的内容:内容不需要区分条目身份,整段追加即可。

何时不用 ​

  • 内容是会话、条目有身份且需要逐条遍历时,使用消息流。
  • 展示结构化记录、需要筛选排序时,使用表格。
  • 展示一段代码时,使用代码视图。

特性 ​

  • 结构四层:root · viewport · content · line;每行内容由作者决定,组件只提供身份与等宽排版。另有两个可选部件:scroll-to-end-trigger 与 live-region。
  • rows 按行数定高。
  • 自动跟随到底部;用户向上翻时停止跟随,回到底部后恢复。
  • 内置“回到底部”:离开底部时出现,按下后归位并重新粘附。留空时皮肤绘制向下的字形,放入节点即替换为自定义图形。
  • 应用设为 data-material="liquid" 时,“回到底部”换成液态面:按下层换色调,按住时液面随手指形变。
  • 视口自身可聚焦,整块日志占一个 Tab 停靠位,方向键与翻页键交给浏览器滚动。

组合 ​

  • 行内可以用文本高亮标出关键词。
  • 给视口一个 id,把滚动条的 controls 指向它,滚动条与视口平级放在 root 内:它浮在内容之上,不占宽度。未挂自绘滚动条时视口自行预留一条通道,原生滚动条出现与消失不会推动文字。

最佳实践 ​

  • 用户向上翻时不强行拉回底部。
  • 行数很大时截断或虚拟化,不把十万行全部挂载。

反模式 ​

  • 每到一行就整块重渲。
  • 不提供复制或下载全部日志的入口。
  • 把每一行都写进播报区,读屏会被逐行打断。

API 参考 ​

产物 ​

层值
自定义元素<xh-log>
Vue 组件XhLogContent XhLogLine XhLogLiveRegion XhLogRoot XhLogScrollToEndTrigger XhLogViewport
组合式函数useLog
状态机logMachine
皮肤@xihan-ui/styles/log.css

Props ​

属性类型必填说明
thresholdnumber距底部多少 px 视为在底部,默认使用贴底原语的默认值。
onStickChange(details: LogStickChangeDetails) => void贴底状态变化时通知宿主。
loadingboolean行仍在传输中:日志区报告 aria-busy,根写 data-loading。
rowsnumber视口按多少行定高;未提供时高度由皮肤决定。
sizeSize尺寸:sm / md / lg。影响行文字号与内衬,行高不随档位变化。
translationsPartial<LogTranslations>

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
stick-changeLogStickChangeDetails贴底状态变化;detail 为 { atBottom: boolean, sticking: boolean }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhLogRootdefaultLogRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhLogLinelevelLogLevel该行的级别,写为行上的 data-level。
XhLogRootchildrenSlotChildren<LogRootSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
scroll-to-end-trigger'visible' | 'hidden'

以下名称仅用于内部状态机。

状态:idle

事件:STICK.CHANGE · SCROLL_TO_BOTTOM · PRESS.START · PRESS.END · TRIGGER.RENDERED

判据:canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
rowsnumber | undefined取整后的行数;rows 缺席或不是正数时为 undefined。
loadingboolean
atBottomboolean当前滚动位置是否落在底部阈值内。
stickingboolean新行到达时是否自动跟随到底部。
showScrollToEndTriggerboolean是否显示回到底部按钮,不在底部时为 true。
scrollToBottom() => void滚动到底部并恢复贴附。
getRootProps() => T['element']
getViewportProps() => T['element']
getContentProps() => T['element']
getLineProps(props?: LogLineProps) => T['element']
getScrollToEndTriggerProps() => T['button']
getLiveRegionProps() => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Tab焦点进入日志区日志区自身可聚焦,方向键/PageUp/PageDown/Home/End 交给浏览器滚动,组件不接管
Space / Enter焦点在"回到底部"按钮上滚回底部并重新粘附
Space / Enter按住"回到底部"按钮且视口不在底部按住期间 scroll-to-end-trigger 投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或回到底部(按钮收起)撤下

ARIA ​

以下属性由 connect 生成。

部件属性值
viewportaria-busy'true' | undefined
viewportaria-labellabel.log
viewportaria-live'off'
viewportrole'log'
scroll-to-end-triggeraria-labellabel.scrollToBottom
live-regionaria-atomic'true'
live-regionaria-live'polite'
live-regionrole'status'
  • 视口是 role=log,但其隐含的 aria-live 被显式关闭:逐行读出连续输出会成为读屏噪声。
  • 播报使用独立的 live-region:宿主决定读哪一句、何时读,例如一段输出结束后读出结论与错误条数。不要把每一行原样写入,否则等于重新打开逐行播报。
  • 成批取行期间视口报告 aria-busy;播报区是视口的兄弟节点,不受其影响。

样式参考 ​

皮肤 ​

@xihan-ui/styles/log.css 使用 [data-scope="log"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-at-bottom''(条件成立时才出现)
rootdata-loading''(条件成立时才出现)
rootdata-sizeprops.size
rootdata-sticking''(条件成立时才出现)
linedata-levelline?.level
scroll-to-end-triggerdata-pressed''(条件成立时才出现)
scroll-to-end-triggerdata-state'visible' | 'hidden'
scroll-to-end-triggerdata-xh-action-control''
scroll-to-end-triggerdata-xh-action-display'always'
scroll-to-end-triggerdata-xh-action-profile'floating'
scroll-to-end-triggerdata-xh-action-size'xs'
scroll-to-end-triggerdata-xh-action-variant'ghost'
scroll-to-end-triggerdata-xh-liquid''
scroll-to-end-triggerdata-xh-material'frosted'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-log-bgrootbackgrounddefault--xh-bg-surfacelog 的 root 部件 background 覆盖槽。
--xh-log-borderrootborderdefault--xh-border-defaultlog 的 root 部件 border 覆盖槽。
--xh-log-content-pxcontentpadding-inlinedefault--xh-_log-content-pxlog 的 content 部件 padding-inline 覆盖槽。
--xh-log-fgrootcolordefault--xh-fg-defaultlog 的 root 部件 color 覆盖槽。
--xh-log-fontcontentfont-familydefault--xh-font-family-monolog 的 content 部件 font-family 覆盖槽。
--xh-log-font-sizecontentfont-sizedefault--xh-_log-font-sizelog 的 content 部件 font-size 覆盖槽。
--xh-log-icon-sizescroll-to-end-trigger--xh-icon-sizedefault--xh-_action-profile-glyph-sizelog 的 scroll-to-end-trigger 部件 --xh-icon-size 覆盖槽。
--xh-log-level-debug-fglinecolorlevel=debug--xh-fg-subtlelog 的 line 部件 color 覆盖槽。
--xh-log-level-error-fglinecolorlevel=error--xh-fg-dangerlog 的 line 部件 color 覆盖槽。
--xh-log-level-info-fglinecolorlevel=info--xh-fg-defaultlog 的 line 部件 color 覆盖槽。
--xh-log-level-warn-fglinecolorlevel=warn--xh-fg-warninglog 的 line 部件 color 覆盖槽。
--xh-log-line-heightline
root
viewport
block-size
line-height
default1.25remlog 的 line、root、viewport 部件 block-size、line-height 覆盖槽。
--xh-log-radiusrootborder-radiusdefault--xh-shape-surfacelog 的 root 部件 border-radius 覆盖槽。
--xh-log-rowsviewportblock-sizedefault16log 的 viewport 部件 block-size 覆盖槽。
--xh-log-scroll-to-end-trigger-bgscroll-to-end-trigger--xh-ink-surface
background-color
default
disabled
focus-visible
xh-ink-surface
--xh-_material-bg
--xh-_material-bg-focus
log 的 scroll-to-end-trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-log-scroll-to-end-trigger-bg-hoverscroll-to-end-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_material-bg-hoverlog 的 scroll-to-end-trigger 部件 background-color 覆盖槽。
--xh-log-scroll-to-end-trigger-borderscroll-to-end-triggerborder
border-color
default
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_material-borderlog 的 scroll-to-end-trigger 部件 border、border-color 覆盖槽。
--xh-log-scroll-to-end-trigger-fgscroll-to-end-triggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_material-fglog 的 scroll-to-end-trigger 部件 color 覆盖槽。
--xh-log-scroll-to-end-trigger-insetscroll-to-end-triggerinset-block-end
inset-inline-end
default--xh-space-3log 的 scroll-to-end-trigger 部件 inset-block-end、inset-inline-end 覆盖槽。
--xh-log-scroll-to-end-trigger-radiusscroll-to-end-triggerborder-radiusdefault--xh-shape-circlelog 的 scroll-to-end-trigger 部件 border-radius 覆盖槽。
--xh-log-scroll-to-end-trigger-shadowscroll-to-end-triggerbox-shadowdefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_material-shadowlog 的 scroll-to-end-trigger 部件 box-shadow 覆盖槽。
--xh-log-scroll-to-end-trigger-sizescroll-to-end-triggerblock-size
inline-size
default
xh-action-profile=floating
--xh-_action-profile-visual-sizelog 的 scroll-to-end-trigger 部件 block-size、inline-size 覆盖槽。
--xh-log-shadowrootbox-shadowdefaultnonelog 的 root 部件 box-shadow 覆盖槽。
--xh-log-tab-sizelinetab-sizedefault4log 的 line 部件 tab-size 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 出现(锚定面板) · 出现(无锚定弹出)(见动效规范)。

共享关键帧 xh-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License