Blessed 技术文档
基本介绍
blessed 是基于 Node.js 的、功能强大且高级的终端界面创建库(Cursed-like library)。它的核心定位是让开发者能够利用 JavaScript,像构建网页应用一样,轻松地在命令行终端中创建复杂、美观、交互丰富的图形用户界面(TUI)
blessed 通过抽象底层的终端控制序列(如 terminfo 和 termcap),提供一套面向对象的 API,开发者可以通过声明式的方式定义和组织各种 UI 组件,而无需关心具体的终端类型和控制代码
pnpm install blessed主要应用场景
blessed 的应用场景非常广泛,几乎涵盖了所有需要在终端中提供丰富交互体验的场合:
- 命令行工具(CLI):为传统的命令行工具增加图形化配置界面、进度显示、实时日志查看等功能,提升用户体验
- 仪表盘(Dashboards):创建实时监控系统的数据仪表盘,例如服务器状态监控、应用性能指标(APM)、加密货币行情看板等
- 交互式终端应用:开发功能完整的终端应用程序,如文件管理器、代码编辑器、聊天客户端、任务管理器等
- 游戏开发:制作复古风格的终端游戏
与其他类似库的对比
blessed 经常被拿来与 ncurses(及其 Node.js 包装)等传统 TUI 库进行比较
| 特性 | blessed | ncurses |
|---|---|---|
| 抽象层次 | 高层抽象。提供了面向对象的组件模型,更接近现代 GUI 框架 | 底层库。提供更基础的窗口和字符控制功能,API 相对原始 |
| 开发效率 | 高。声明式 API 和丰富的内置组件可以快速构建复杂界面 | 较低。需要手动管理窗口、屏幕刷新和事件循环,开发周期较长 |
| 布局系统 | 现代化。支持类似 CSS 的布局和样式配置,支持百分比、相对定位等 | 基础。通常需要手动计算坐标和尺寸 |
| 事件模型 | 强大。内置了完善的事件系统,支持鼠标、键盘、窗口事件 | 基础。事件处理相对简单,需要开发者自行实现复杂的事件逻辑 |
| 社区生态 | 活跃。拥有丰富的第三方插件和组件,如 blessed-contrib | 非常成熟。作为事实上的标准,拥有庞大的用户基础和长期的技术积累 |
| 性能 | 良好。针对 Node.js 环境进行了优化,但对于极端性能场景可能不如 C/C++ 库 | 极高。作为 C 语言库,性能非常出色 |
总而言之,blessed 以牺牲部分底层控制和极致性能为代价,换来了极高的开发效率和更现代化的开发体验,特别适合需要快速迭代和构建复杂界面的 Node.js 项目
技术特性
支持的终端 UI 组件类型
blessed 内置丰富的 UI 组件(官方称为 Element 或 Node),基本可以满足绝大部分 TUI 应用的需求。所有组件都继承自基类 Element 和 Node
基础容器
Element: 所有可见组件的基类,提供基础的样式和布局能力Box: 一个简单的矩形容器,可用于布局和内容承载,类似 HTML 中的<div>Screen: 根节点,代表整个终端屏幕,是所有其他组件的顶级容器
文本与内容
Text: 显示纯文本内容Line: 绘制水平或垂直的线条BigText: 使用 ASCII 字符绘制大字体文本Log: 可滚动的日志输出组件,适合显示实时日志Markdown: 解析并渲染 Markdown 格式的文本
交互式组件
List: 可选择的列表,支持键盘导航Listbar: 水平菜单栏,类似传统 GUI 应用的顶部菜单File-Manager: 一个简单的文件浏览器组件Form: 表单容器,用于管理输入型组件Input,Textbox,Textarea: 不同形式的文本输入框Button: 可点击的按钮Checkbox: 复选框RadioSet&RadioButton: 单选按钮组Prompt: 弹出一个提示框,等待用户输入Question: 弹出一个确认对话框(Yes/No)
数据可视化
ProgressBar: 进度条Table: 渲染表格数据ListTable: 结合了List和Table的功能,即可选择的表格
其他
Terminal: 在blessed界面中嵌入一个真实的终端会话Image: 显示图片(通过 Sixel 图形格式或 iTerm2 的内联图像协议)
事件处理机制
blessed 拥有一个强大的、类似 DOM 的事件系统。每个组件都是一个事件发射器(EventEmitter),可以监听和响应各种事件
键盘事件: screen 对象和可聚焦的组件(如 Input、List)可以监听键盘事件
keypress: 任何按键被按下时触发key [name]: 特定按键被按下时触发,例如key enteronce('keypress', ...): 监听一次按键事件
鼠标事件: 需要在创建 screen 时设置 sendFocus: true 和 smartCSR: true 来启用鼠标支持
mousedown,mouseup,mousemove: 鼠标按键按下、抬起和移动wheeldown,wheelup: 鼠标滚轮事件click: 组件被点击时触发focus,blur: 组件获得或失去焦点时触发
组件生命周期与状态事件:
attach,detach: 组件被添加或移除出渲染树。show,hide: 组件可见性改变。move,resize: 组件位置或尺寸改变。prerender,render: 组件渲染前后触发。
事件可以进行冒泡。如果在子组件上触发了一个事件,但没有被处理,它会向上传递到父组件,直到 screen 对象。可以通过 event.stopPropagation() 来阻止事件冒泡。
布局系统与样式配置方式
blessed 的布局和样式系统非常灵活,其设计思想借鉴了 CSS
尺寸与定位:
- 绝对定位:
top,left,right,bottom可以是数字(字符数)或百分比字符串('50%') - 尺寸:
width,height同样支持数字和百分比 - 相对定位: 可以将
top,left等设置为相对于父容器中心的值,例如left: 'center' - 偏移量: 支持在百分比或中心对齐的基础上进行微调,例如
top: '50%-5'
样式配置 (style 对象):
- 颜色:
fg(前景色),bg(背景色)。可以是颜色名称('red')、十六进制值('#ff0000')或 256 色索引 - 文本属性:
bold,underline,blink,inverse(反色) - 边框 (
border对象):type:'line'(默认) 或'bg'(使用背景色填充)ch: 用于绘制边框的字符,默认为(空格)fg,bg: 边框颜色。
- 状态样式:
style对象中可以包含hover(鼠标悬停),focus(获得焦点),selected(列表项被选中) 等伪类的样式
const box = blessed.box({
parent: screen,
top: "center",
left: "center",
width: "80%",
height: "90%",
border: {
type: "line",
fg: "cyan"
},
style: {
fg: "white",
bg: "blue",
hover: {
bg: "green"
}
}
})跨平台兼容性说明
blessed 致力于提供良好的跨平台兼容性,能在绝大多数现代终端模拟器上运行,包括:
- Linux:
xterm、gnome-terminal、konsole、terminator等 - macOS:
Terminal.app、iTerm2 - Windows:
cmd.exe、PowerShell、Windows Terminal。在 Windows 上,blessed会尝试使用windows-curses作为后端,以提供更好的兼容性
尽管 blessed 做了很多兼容性工作,但某些高级特性(如图片显示、24 位真彩色)仍然依赖于特定终端的支持
- 颜色支持:
blessed会自动检测终端支持的颜色数量(2、8、16、256、16777216),并尽可能优雅降级 - 字符集: 建议使用 UTF-8 编码的终端,以确保特殊字符和宽字符(如中文)能正确显示
在开发时,如果遇到平台相关的显示问题,通常需要检查终端本身的配置(如字体、编码、TERM 环境变量等)
核心 API 文档
blessed 的 API 设计是面向对象的,理解其核心类和方法是高效开发的关键
关键类与方法说明
blessed.screen(options)
screen 是 blessed 应用的根节点和心脏。它继承自 Box,并负责管理全局状态、事件循环和屏幕渲染
-
构造函数:
blessed.screen(options)options(Object): 配置对象。smartCSR(Boolean):true(默认) 启用智能光标渲染优化,可以大幅提升性能。sendFocus(Boolean):true(默认) 使能focus和blur事件,是鼠标支持所必需的。autoPadding(Boolean):true(默认) 自动为带边框的元素计算内边距。dockBorders(Boolean):true(默认) 边框停靠在元素边缘。fullUnicode(Boolean):true(默认) 开启完整的 Unicode 支持,正确处理宽字符。terminal(String): 指定终端类型,覆盖process.env.TERM。
-
关键方法:
screen.render(): 手动触发一次全屏重绘。blessed会自动批量处理更新,但有时需要手动调用。screen.destroy(): 销毁screen和所有子元素,恢复终端到原始状态。screen.key(keys, listener): 注册全局快捷键。例如screen.key(['q', 'C-c'], () => process.exit(0));。screen.append(element): 将一个元素添加到screen中。screen.remove(element): 从screen中移除一个元素。
blessed.node(options) (基类)
Node 是所有组件的顶层基类,主要负责组件树的层级关系(父子、兄弟节点)
-
核心属性:
parent: 父节点children: 子节点数组type: 组件类型字符串(如'box')
-
核心方法:
append(element): 添加一个子节点prepend(element): 在子节点列表的开头添加一个节点remove(element): 移除一个子节点insert(element, index): 在指定索引处插入一个子节点emit(event, ...args): 触发一个事件on(event, listener): 监听一个事件
blessed.element(options) (基类)
Element 继承自 Node,是所有可见组件的基类。它添加了位置、尺寸、样式和内容等视觉相关的属性和方法
-
核心属性:
options(Object): 包含所有配置,如top,left,width,height,style,border,content等width,height,top,left: 最终计算出的尺寸和位置content(String): 组件显示的文本内容
-
核心方法:
show(): 显示组件hide(): 隐藏组件toggle(): 切换可见性focus(): 使组件获得焦点setContent(text): 设置组件的文本内容setFront(): 将组件置于其兄弟节点的顶层setBack(): 将组件置于其兄弟节点的底层
blessed.box(options)
Box 是最常用的基础容器组件,继承自 Element。它本身没有太多特殊功能,主要用于布局和作为其他组件的容器
常用配置参数详解
以下是一些在创建组件时最常用的配置参数:
parent(Node): 必需。指定该组件的父节点,通常是screen或另一个容器组件top,left,right,bottom(Number | String): 控制位置。可以是数字(行/列),或字符串('center','50%','50%-2')width,height(Number | String): 控制尺寸。规则同上content(String): 要显示的文本内容。可以使用blessed的标签语法进行样式设置,例如'Hello {bold}world{/bold}!'label(String | Object): 在边框上显示的标签。如果是对象,可以设置{ text: 'Label', side: 'left' }border(Object | String): 边框样式。如果是字符串,则指定type。对象可以包含type,ch,fg,bgstyle(Object): 样式对象,包含fg,bg,bold,underline,hover,focus等hidden(Boolean):true则初始状态为隐藏scrollable(Boolean):true使内容可滚动(通过鼠标或按键)keys(Boolean):true使组件可以接收键盘事件mouse(Boolean):true使组件可以接收鼠标事件vi(Boolean):true为可滚动组件启用vi风格的导航键(k,j,g,G)tags(Boolean):true(默认) 启用内容中的标签语法
生命周期管理最佳实践
-
总是指定
parent: 创建任何组件时,都必须明确其parent。这能确保组件被正确地添加到组件树中并进行渲染。 -
使用
screen.destroy()清理: 当你的应用程序退出时,务必调用screen.destroy()。这会恢复终端到原始模式,移除所有监听器,并销毁所有子组件,防止终端状态混乱。 -
合理使用
render():blessed会在内部对render调用进行优化和节流。通常你不需要频繁地手动调用screen.render()。只有在进行了大量同步更新后,希望立即看到变化时,才需要手动调用。 -
动态添加和移除组件: 使用
parent.append(child)和child.destroy()(或parent.remove(child)) 来动态管理界面元素。destroy()会彻底清理组件及其子组件,并移除所有相关的监听器,是比remove更彻底的清理方式。 -
事件监听器管理: 在
destroy组件时,blessed会自动移除该组件上通过.on()方法添加的监听器。这避免了常见的内存泄漏问题
// 退出逻辑
screen.key(["q", "C-c"], (ch, key) => {
// 在退出前销毁 screen 对象
return screen.destroy()
})代码示例
理论结合实践是最好的学习方式。以下是一些从简到繁的代码示例
基础 Hello World
这是一个最简单的 blessed 应用,它会在屏幕中央显示一个带边框的文本框
// hello-world.js
const blessed = require("blessed")
// 创建一个 screen 对象
const screen = blessed.screen({
smartCSR: true,
title: "Hello Blessed!"
})
// 创建一个 box 组件
const box = blessed.box({
parent: screen,
top: "center",
left: "center",
width: "50%",
height: "50%",
content: "Hello {bold}world{/bold}!",
tags: true,
border: {
type: "line"
},
style: {
fg: "white",
bg: "magenta",
border: {
fg: "#f0f0f0"
}
}
})
// 监听 'q' 或 'Ctrl+C' 按键事件,用于退出程序
screen.key(["q", "C-c"], (ch, key) => {
return process.exit(0)
})
// 渲染屏幕
screen.render()List 组件
创建 list.js
- 在 blessed 里根组件是 screen,所有的小组件都要
screen.append来添加 - 指定
fullUnicode: true这样可以支持中文字符 - 然后创建 list 组件,指定 width、height、align、border,还有颜色,以及 items 数据
- blessed 可以通过键盘控制,还支持鼠标控制
import blessed from "blessed"
const screen = blessed.screen({
fullUnicode: true
})
const data = [
"白夜行",
"解忧杂货店",
"挪威的森林",
"追风筝的人",
"小王子",
"飘",
"麦田里的守望者",
"时间简史",
"人类简史",
"活着为了讲述",
"白夜行",
"百鬼夜行"
]
const list = blessed.list({
width: "50%",
height: "50%",
border: "line",
label: "书籍列表",
align: "left",
right: 0,
bottom: 0,
keys: true,
mouse: true,
style: {
fg: "white",
bg: "default",
selected: {
bg: "blue"
}
},
items: data
})
screen.append(list)
list.select(0)
list.on("select", function (item) {
screen.destroy()
console.log(item.getText())
})
screen.key("C-c", function () {
screen.destroy()
})
list.focus()
screen.render()运行文件:
node list.jsForm 组件
创建 form.js
const blessed = require("blessed")
const screen = blessed.screen({
fullUnicode: true
})
const prompt = blessed.prompt({
parent: screen,
border: "line",
height: "shrink",
width: "half",
top: "center",
left: "center",
label: " {blue-fg}登录{/blue-fg} ",
tags: true
})
const msg = blessed.message({
parent: screen,
border: "line",
width: "half",
height: "shrink",
top: "center",
left: "center",
label: " {blue-fg}提示{/blue-fg} ",
tags: true,
hidden: true
})
prompt.input("你的用户名?", "", function (err, username) {
prompt.input("你的密码?", "", function (err, password) {
if (username === "guang" && password === "aaa123") {
msg.display("登录成功!", 1)
} else {
msg.display("用户名或密码错误!", 1)
}
setTimeout(function () {
screen.destroy()
console.log(username, password)
}, 1000)
})
})
screen.key("C-c", function () {
screen.destroy()
})
screen.render()File Manager
创建 filemanger.js
import blessed from "blessed"
const screen = blessed.screen({
fullUnicode: true
});
const fm = blessed.filemanager({
parent: screen,
border: 'line',
height: 'half',
width: 'half',
top: 'center',
left: 'center',
label: ' {blue-fg}%path{/blue-fg} ',
cwd: process.cwd(),
keys: true,
style: {
selected: {
bg: 'blue'
}
},
scrollbar: {
bg: 'white'
}
});
fm.on('file', (file)=> {
screen.destroy();
console.log(file);
})
screen.key('C-c', function() {
screen.destroy();
});
fm.refresh();
screen.render();
Table 组件
创建 table.js
const blessed = require("blessed")
const screen = blessed.screen({
fullUnicode: true
})
const table = blessed.table({
parent: screen,
width: "80%",
height: "shrink",
top: "center",
left: "center",
data: null,
border: "line",
align: "center",
tags: true,
style: {
border: {
fg: "white"
},
header: {
fg: "blue",
bold: true
},
cell: {
fg: "green"
}
}
})
const data = [
["姓名", "性别", "年龄", "电话号码"],
["东东", "男", "20", "13233334444"],
["光光", "男", "20", "13233332222"],
["小红", "女", "21", "13233335555"],
["小刚", "男", "22", "13233336666"]
]
data[1][0] = "{red-fg}" + data[1][0] + "{/red-fg}"
table.setData(data)
screen.key("C-c", function () {
screen.destroy()
})
screen.render()ProgressBar 组件
创建 progressbar.js
import blessed from "blessed"
const screen = blessed.screen({
fullUnicode: true
});
const progressBar = blessed.progressbar({
parent: screen,
top: '50%',
left: '50%',
height: 2,
width: 20,
style: {
bg: 'gray',
bar: {
bg: 'green'
}
}
})
screen.key('C-c', function() {
screen.destroy();
});
let total = 0;
const timer = setInterval(() => {
if(total === 100) {
clearInterval(timer);
}
progressBar.setProgress(total)
screen.render();
total += 2;
}, 100);
screen.render();性能优化指南
尽管 blessed 提供了很高的开发效率,但在构建复杂或高刷新率的 TUI 应用时,性能问题仍然值得关注。以下是一些关键的性能优化技巧。
渲染性能优化技巧
-
保持
smartCSR: true:这是最重要的性能开关,它在
screen的默认配置中是开启的。smartCSR(Smart Cursor and Screen Rendering) 机制会计算出屏幕上真正发生变化的区域,并只更新这部分内容,而不是每次都重绘整个屏幕。这能极大地减少终端的 I/O 操作 -
减少不必要的
screen.render()调用:blessed会在事件循环的下一个 tick 中自动批量处理更新并调用render。除非你确实需要立即看到更新,否则应避免手动、高频地调用screen.render()。例如,在一个循环中更新多个组件时,只需在循环结束后让blessed自动渲染一次即可 -
使用
box作为静态背景:如果你的界面有复杂的静态背景(例如,由很多
box组成的边框或图案),可以考虑将它们组合在一个大的box中,并尽量避免重绘它。将动态内容放在这个静态背景的上层 -
隐藏不可见的元素:
如果一个元素暂时不需要显示,使用
element.hide()将其隐藏。被隐藏的元素不会参与渲染计算,可以减轻blessed的工作量。对于弹出式窗口或标签页界面,这一点尤其重要 -
避免复杂的文本标签 (
tags):虽然
{bold}等标签很方便,但blessed需要解析这些标签并转换为终端序列。对于需要频繁更新的大段文本,如果其中不包含样式变化,可以考虑设置tags: false来关闭标签解析,从而获得微小的性能提升
内存管理注意事项
-
及时销毁不再使用的组件:
当一个组件(尤其是包含大量子组件或持有大量数据的组件,如
Log或List)不再需要时,应该调用element.destroy()来销毁它。destroy方法会:- 从其父节点中移除自己
- 递归销毁所有子节点
- 移除自身所有的事件监听器 这能有效地释放内存,防止内存泄漏
-
谨慎处理
Log组件:Log组件默认会缓存所有添加的日志行,以便用户可以回滚查看。如果日志量非常大,这可能会消耗大量内存。可以考虑:- 设置
scrollback选项,限制缓存的行数 - 定期手动清理
Log内容,例如logger.setContent('')
- 设置
-
管理外部数据引用:
确保在组件的事件监听器中引用的外部对象能够被垃圾回收。如果一个组件被销毁了,但外部还有一个对象持有对它的引用,或者它的监听器闭包中引用了不会被释放的外部对象,都可能导致内存泄漏
大尺寸界面处理建议
-
分页和虚拟滚动:
对于需要显示大量数据(成千上万行)的列表或表格,不要一次性将所有数据都添加到组件中。应该实现分页逻辑,或者像虚拟滚动一样,只向组件中添加当前视口内可见的数据项。当用户滚动时,动态地更新组件的内容
-
延迟加载组件:
对于复杂的、由多个页面或标签组成的 TUI,可以采用延迟加载的策略。只在用户切换到某个页面或标签时,才创建和渲染该页面所需的组件,而不是在启动时就创建所有组件
-
简化布局计算:
blessed的百分比和center布局非常方便,但每次render都需要重新计算。对于一个固定的、网格化的布局,可以考虑在启动时计算好所有组件的绝对位置和尺寸,之后不再改变。这可以减少blessed在渲染时的布局计算开销 -
测试不同终端的性能:
不同的终端模拟器在处理大量数据输出时的性能差异可能很大。例如,
iTerm2和Alacritty(GPU 加速) 通常比Terminal.app或xterm在高吞吐量下的表现更好。如果你的应用对性能要求很高,建议在目标用户常用的高性能终端上进行测试和优化
生态系统
blessed 拥有一个虽然不大但相当活跃的生态系统,提供了许多实用的插件和工具,可以极大地扩展其功能。
blessed 官方并未维护一个正式的插件列表,但其社区贡献了大量高质量的模块。
常用第三方扩展
-
blessed-contrib: 这是blessed生态系统中最著名、最强大的扩展包。它提供一系列用于构建仪表盘(Dashboard)的高级组件,包括:Grid: 一个网格布局系统,可以轻松地将屏幕划分为多个区域Line Chart: 实时折线图Bar Chart: 柱状图Stacked Bar Chart: 堆叠柱状图Map: 在终端中显示世界地图Donut: 甜甜圈图Gauge: 仪表盘式的计量器Sparkline: 紧凑的迷你折线图LCD Display: 模拟 LCD 数字显示效果
blessed-contrib是构建监控类 TUI 应用的首选工具 -
blessed-xterm: 允许你在blessed应用中嵌入一个功能完整的xterm终端。这比blessed内置的Terminal组件功能更强大,因为它直接使用了node-pty和xterm.js的部分代码,提供了更真实的终端模拟 -
blessed-react: 使用 React 的声明式语法和 JSX 来构建blessed界面。它将 React 组件的生命周期和状态管理与blessed的渲染能力结合起来,使得构建复杂的、状态驱动的 TUI 更加方便 -
blessed-vue: 类似于blessed-react,但面向 Vue.js 开发者。它允许你使用 Vue 的模板语法和组件化思想来开发 TUI
调试工具链
调试 blessed 应用可能比调试传统的 Node.js 应用要棘手一些,因为程序的输出被 blessed 的界面占用了。以下是一些有效的调试方法:
-
使用日志文件: 这是最简单直接的方法。不要使用
console.log(因为它会扰乱blessed的屏幕渲染),而是将调试信息写入到一个单独的日志文件中。javascriptconst fs = require("fs") function debugLog(message) { fs.appendFileSync("debug.log", `[${new Date().toISOString()}] ${message}\n`) } // 在你的代码中 debugLog("Something happened") -
使用
blessed.log组件: 在你的界面中专门开辟一个Log组件用于调试。你可以将需要观察的变量或事件信息实时输出到这个组件中。 -
Node.js 内置调试器: 你可以使用 Node.js 的内置调试器。通过
node inspect your-app.js启动应用。然后你可以在另一个终端或 Chrome DevTools 中连接到调试服务。但这对于 TUI 来说可能体验不佳,因为断点会冻结整个界面。 -
VS Code 调试器: 配置 VS Code 的
launch.json文件来进行调试是比较推荐的方式。你需要将调试控制台设置为一个外部或集成的终端,而不是默认的调试控制台。json// .vscode/launch.json { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Blessed App", "program": "${workspaceFolder}/your-app.js", "console": "integratedTerminal" // 或 "externalTerminal" } ] }这样,你的
blessed应用会运行在一个独立的终端窗口中,而你可以在 VS Code 中设置断点、检查变量。
典型组件组合案例
这个例子演示组合使用 Listbar、List 和 Box 来创建一个简单的交互式应用
// layout-example.js
const blessed = require("blessed")
const screen = blessed.screen({
smartCSR: true,
title: "Component Layout Example"
})
// 顶部菜单栏
const menu = blessed.listbar({
parent: screen,
top: 0,
left: 0,
right: 0,
height: 1,
items: {
File: () => showContent("File Content"),
Edit: () => showContent("Edit Content"),
View: () => showContent("View Content"),
Help: () => showContent("Help Content"),
Exit: () => process.exit(0)
},
style: {
bg: "blue",
item: {
bg: "red",
hover: {
bg: "green"
}
},
selected: {
bg: "green"
}
}
})
// 内容显示区域
const contentBox = blessed.box({
parent: screen,
top: 1,
left: 0,
right: 0,
bottom: 0,
content: "Select an item from the menu...",
padding: 1,
style: {
bg: "#222"
}
})
function showContent(text) {
contentBox.setContent(text)
screen.render()
}
screen.key(["q", "C-c"], () => process.exit(0))
menu.focus()
screen.render()高级功能实现示范
此示例展示使用 Log 组件实时显示日志,并使用 Prompt 与用户进行交互
// advanced-example.js
const blessed = require("blessed")
const screen = blessed.screen({ smartCSR: true, title: "Advanced Example" })
// 日志窗口
const logger = blessed.log({
parent: screen,
top: 0,
left: 0,
width: "70%",
height: "100%",
border: "line",
label: "Real-time Log",
scrollable: true,
alwaysScroll: true,
scrollbar: { ch: " ", track: { bg: "cyan" } },
keys: true,
vi: true
})
// 控制按钮
const sendButton = blessed.button({
parent: screen,
top: 1,
right: 1,
width: "25%",
height: 3,
content: "Send Command",
align: "center",
valign: "middle",
border: "line",
style: {
fg: "white",
bg: "green",
hover: { bg: "darkgreen" }
}
})
// 模拟日志输出
let logCounter = 0
setInterval(() => {
logger.log(`Log entry ${++logCounter} at ${new Date().toLocaleTimeString()}`)
}, 1000)
// 按钮点击事件
sendButton.on("press", () => {
const prompt = blessed.prompt({
parent: screen,
top: "center",
left: "center",
height: "shrink",
width: "shrink",
border: "line",
label: "Input Command"
})
prompt.input("Enter command:", "", (err, value) => {
if (value) {
logger.log(`> Command received: ${value}`)
}
screen.render()
})
})
screen.key(["q", "C-c"], () => process.exit(0))
screen.render()