Textual-vuepy 组件总览
Textual-vuepy 将 Textual 全部内置 Widget 封装为 Vue.py 组件,让你在 .vue SFC 文件中以声明式方式构建终端 TUI 应用。
安装:
pip install 'vuepy-core[textual]'导入:在
create_app时指定backend='textual',vtextual 插件将自动注册所有组件。
布局组件
| 组件 | 说明 | 常用属性 |
|---|---|---|
VBox | 垂直容器,子组件从上到下排列 | style, id |
HBox | 水平容器,子组件从左到右排列 | style, id |
Slot | VBox 的别名,用于 slot 占位 | — |
Collapsible | 可折叠容器 | title, collapsed |
TabbedContent | 标签页容器(配合 TabPane) | initial |
TabPane | 标签页内容区域 | title, id |
ContentSwitcher | 根据 ID 切换显示内容 | initial |
基础组件
| 组件 | v-model 默认绑定 | 说明 | 常用属性 |
|---|---|---|---|
Button | label | 按钮 | label, disabled, variant |
Label | label | 文本标签,支持 Rich markup | label |
Static | renderable | 静态文本/Rich 渲染 | renderable, markup |
Link | url | 超链接 | url, label |
Placeholder | — | 占位符 | label |
表单组件
| 组件 | v-model 默认绑定 | 说明 | 常用属性 |
|---|---|---|---|
Input | value | 单行输入框 | value, placeholder, password, disabled, compact |
TextArea | text | 多行文本编辑器 | text, language, code_editor |
MaskedInput | value | 格式化输入框(掩码) | template, value |
Checkbox | value | 复选框 | label, value, disabled |
RadioButton | value | 单选按钮 | label, value |
RadioSet | pressed | 单选按钮组 | — |
Select | value | 下拉选择框 | options, value, prompt |
SelectionList | selected | 多选列表 | — |
Switch | value | 开关 | value, animate |
数据展示
| 组件 | v-model 默认绑定 | 说明 | 常用属性 |
|---|---|---|---|
DataTable | data | 数据表格 | cols, rows |
Markdown | markdown | Markdown 渲染 | markdown |
MarkdownViewer | markdown | Markdown 查看器(含目录) | markdown, show_table_of_contents |
Pretty | object | Python 对象格式化展示 | object |
Sparkline | data | 迷你折线图 | data, summary_function |
Digits | value | 大号数字显示(类 LCD) | value |
RichLog | lines | 富文本滚动日志 | markup, wrap, highlight |
Log | lines | 纯文本滚动日志 | — |
导航组件
| 组件 | 说明 | 常用属性 |
|---|---|---|
Tabs | 顶部标签页导航 | active |
Tab | 单个标签(在 Tabs 内使用) | label, id |
ListView | 可滚动列表视图 | initial_index |
ListItem | 列表项(在 ListView 内使用) | — |
OptionList | 可选项列表 | — |
Option | 选项项(在 OptionList 内使用) | prompt, id |
SelectionList | 多选列表 | — |
Selection | 多选项(在 SelectionList 内使用) | prompt, value |
Tree | 树形结构 | label |
DirectoryTree | 目录树 | path |
反馈组件
| 组件 | 说明 | 常用属性 / 方法 |
|---|---|---|
Dialog | 模态对话框 | ref 调用 .open() / .close(),或 v-model:value |
LoadingIndicator | 加载动画指示器 | — |
ProgressBar | 进度条 | progress, total, show_eta |
Tooltip | 工具提示 | message |
结构/布局辅助
| 组件 | 说明 |
|---|---|
Header | 应用顶部标题栏 |
Footer | 应用底部状态栏(显示快捷键) |
HelpPanel | 侧边帮助面板 |
KeyPanel | 按键面板(显示已注册的快捷键) |
Rule | 水平分隔线 |
Welcome | 欢迎页面 |
Display | 直接显示已有的 Textual Widget 实例 |
自定义 SFC 组件
Textual-vuepy 提供两个开箱即用的 SFC 自定义组件(位于 textual_vuepy/components/):
ShimmerText — 闪光文字
显示带有流光高亮动画的文字(适合 AI 思考状态展示)。
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | str | "Thinking" | 显示的文字 |
highlight_width | int | 3 | 高亮区域宽度(字符数) |
interval | float | 0.1 | 动画帧间隔(秒) |
running | bool | False | 是否运行动画 |
Spinner — 加载旋转器
显示带旋转字符动画的加载指示器。
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | str | "Loading..." | 旋转符号后的文字 |
interval | float | 0.1 | 动画帧间隔(秒) |
running | bool | False | 是否运行动画 |
style | str | "" | TCSS 样式字符串 |
内置应用(VuepyAppStore)
安装 textual_vuepy 后自动注册以下应用,可通过 vuepy run <name> 启动:
| 名称 | 说明 |
|---|---|
playground | 交互式 SFC 编辑器 + 实时预览,支持拖拽分隔条、文件热重载、目录树 |
keys | 按键事件查看器,显示每次按键的 Key 事件详情 |
sh
vuepy run playground # 启动 SFC Playground
vuepy run keys # 启动按键查看器VueUse 组合式函数
从 textual_vuepy.vueuse 导入:
| 函数 | 说明 | 返回值 |
|---|---|---|
onKeyStroke(key, cb) | 注册全局按键监听(在 onMounted 后生效),cb 不接收参数 | — |
useMouse() | 追踪鼠标屏幕坐标 | (x: Ref[int], y: Ref[int]) |
onKeyStroke 的 key 用的是 Textual 按键名(, 要写成 comma),可以用 vuepy run keys 探测; 被 Textual 占用的键(ctrl+c / ctrl+q / ctrl+p)监听不到。详见 VueUse 组合式函数。
事件处理
Textual-vuepy 支持 Textual 原生事件,通过 @事件名 绑定:
事件名使用蛇形命名(snake_case),对应 Textual Message 类名转小写,如:
Input.Submitted→@input_submittedDirectoryTree.FileSelected→@directory_tree_file_selectedButton.Pressed→@click(Button 有简化别名)
@keyup 监听按键
按键事件不走 Message 名,而是用 @keyup.<按键名> 绑定,修饰键以 . 连接、最后一段是主键:
底层是 Textual 的 widget binding:@keyup.ctrl.shift.c 会在该组件上注册 ctrl+shift+c 绑定,按键触发时调用对应表达式。
用 vuepy run keys 探测按键名
按键名是 Textual 的名字,写错了不会报错、只是不触发。不确定时用内置的 keys 应用探一下:
sh
vuepy run keys按下目标按键或组合键,日志会打印本次的 Key 事件,其中 name= 的值就是写进 @keyup. 的按键名:
text
Key(key='comma', character=',', name='comma', is_printable=True)
Key(key='question_mark', character='?', name='question_mark', is_printable=True)
Key(key='ctrl+a', character=None, name='ctrl_a', is_printable=False)
Key(key='ctrl+shift+c', character=None, name='ctrl_shift_c', is_printable=False)
Key(key='f5', character=None, name='f5', is_printable=False)对应写法:
| 实际按键 | keys 应用里的 name= | 模板写法 |
|---|---|---|
, | comma | @keyup.comma |
? | question_mark | @keyup.question_mark |
[ | left_square_bracket | @keyup.left_square_bracket |
f5 | f5 | @keyup.f5 |
大写 A | upper_a | @keyup.shift.a |
| ctrl+a | ctrl_a | @keyup.ctrl.a |
| ctrl+shift+c | ctrl_shift_c | @keyup.ctrl.shift.c |
规则:把 name= 里分隔修饰键的下划线换成 .,按键名自身的下划线原样保留(question_mark、left_square_bracket)。@keyup.ctrl_a 这种写法不会报错也不会触发,必须写成 @keyup.ctrl.a。
大写字母是唯一的例外:keys 应用显示 name='upper_a',但模板里要写 @keyup.shift.a(@keyup.upper_a 不触发)。
完整的符号名对照表见 VueUse 文档(onKeyStroke 与 @keyup 用的是同一套按键名,区别只是 onKeyStroke 直接写 'ctrl+a')。
焦点决定能不能触发
widget binding 只在该组件自身或它的子组件持有焦点时被检查,因此:
- 容器默认不可聚焦,要加
:can_focus="True",并在onMounted里主动focus()。应用启动时焦点默认落在最外层滚动容器上,它是你这个容器的祖先而不是子组件,不会触发绑定; - 按键沿焦点链向祖先冒泡,所以把
@keyup挂在外层容器、焦点停在里面的Button上也能触发; - 焦点在
Input/TextArea上时,可打印字符被输入控件消费,只有组合键能冒泡到祖先容器; - 什么都没聚焦时不触发;需要全局快捷键请改用
onKeyStroke(注册在 App 上)。
Textual 占用的按键
ctrl+c(提示退出)、ctrl+q(退出应用)、ctrl+p(命令面板)被 Textual 自己占用,监听不到,挑其他键即可(例如 ctrl+t)。
深入了解
- 快速上手 — 安装、运行、第一个应用
- Textual 官方文档 — 了解底层 Widget 的完整属性与事件
- Vue.py 指南 — 响应式、组件通信、插槽等核心概念