告别手写对话面板:TinyRobot Container 单个组件搞定 AI 聊天界面搭建
想必您一定开发过对话面板——用 v-if 控制显示与隐藏,搭配标题栏、消息列表、输入框,再集成全屏切换、关闭按钮、主题适配……单个功能拆开来看并不复杂,但一旦组合起来,代码量便会急剧增长,维护成本更会随着功能迭代呈指数级攀升。
是否存在一个组件,能够将这些能力全部囊括?
答案是肯定的。TinyRobot 的 Container 组件正是为此而生。它不负责渲染对话内容本身——那是 Bubble 和 Sender 的职责——它只专注于三件事:控制面板显隐、编排布局结构、桥接子组件事件。简而言之:
接下来,我们将从源码出发,深入拆解 Container 的三层能力,解析其设计理念,并展示如何在项目中高效运用它。
3 分钟快速上手:用 Container 构建首个完整对话界面
先来看一个最小可运行示例。仅需 Container + BubbleList + Sender 三个组件即可:
复制代码
这就是一个功能完备的对话界面。Container 提供了标题栏、关闭按钮、全屏切换以及底部输入区的固定布局,您只需将消息列表放入默认插槽,将输入框放入 #footer 插槽即可。

对比手动编写等价面板:您需要自行管理 v-if/v-show、编写标题栏 HTML、实现全屏切换逻辑、固定底部输入区布局、适配主题变量——至少需要多写 40 行模板代码和 20 行逻辑代码。Container 将这些工作全部内聚封装。
Container 的三层能力:显隐控制 → 布局编排 → 事件桥接
这是本文的核心观点:Container 的所有设计均可归入三层能力,每一层解决一类典型痛点。
第一层:显隐控制——v-model:show 与 @close
复制代码// 源码核心(简化版)
const show = defineModel<boolean>('show', { required: true })const handleClose = () => {
show.value = false
emit('close')
}
v-model:show 实现了双向绑定——父组件控制面板的打开与关闭,Container 内部的关闭按钮也能反向更新父组件状态。这并非简单的 v-if,而是状态所有权归父组件、触发权归双方的设计模式。
@close 事件在面板关闭时触发,便于您在关闭时执行清理操作(如中断流式响应、保存草稿等),无需额外 watch show 的变化。
第二层:布局编排——标题栏、底部输入区、内容区自动伸缩
查看模板结构:
复制代码<div class="tr-container">
<div class="tr-container__dragging-bar-wrapper">...div>
<div class="tr-container__header">
<slot name="title">
<h3 class="tr-container__title">{{ props.title }}h3>
slot>
<div class="tr-container__header-operations">
<slot name="operations">slot>
<icon-button :icon="fullscreenToggleIcon" @click="..." />
<icon-button :icon="IconClose" @click="handleClose" />
div>
div>
<slot>slot>
<div class="tr-container__footer">
<slot name="footer">slot>
div>
div>
关键的布局逻辑体现在 CSS 中:
复制代码.tr-container {
display: flex;
flex-direction: column;
/* 固定定位,占满视口右侧 */
position: fixed;
inset: 0;
left: var(--left); /* 侧边栏模式:left 不为 0;全屏模式:left 为 0 */
}.tr-container__header + * {
flex: 1; /* 内容区自动填满剩余空间 */
overflow-y: auto; /* 内容溢出自动滚动 */
}.tr-container__footer {
flex-shrink: 0; /* 底部输入区固定,不被挤压 */
}
这一布局编排的核心意图是:标题栏固定在顶部、输入区固定在底部、中间消息列表自动伸缩并支持滚动。您无需编写一行 CSS 即可获得这一完整布局。
第三层:事件桥接——Container 如何将子组件事件向上传递
Container 本身仅 emit 一个 close 事件,但它在事件桥接方面扮演着更重要的角色:它定义了对话面板的交互边界。
当您将 Sender 放置在 #footer 插槽中时,Sender 的 @submit 事件直接由父组件处理——Container 不会拦截。这是有意为之的设计:Container 只负责"壳"的交互(关闭、全屏),不干预"内容"的交互(发送消息、点击气泡)。这种职责隔离使得 Container 无需了解子组件的具体 API,从而保持了组件的通用性。
完整 Props / Events / Slots 速查表
| 类别 | 名称 | 类型 | 说明 |
|---|---|---|---|
| Model | v-model:show | boolean | 面板显隐状态(必填) |
| Model | v-model:fullscreen | boolean | 全屏模式(可选) |
| Prop | title | string | 标题栏文字,默认 'OpenTiny NEXT' |
| Event | close | () => void | 面板关闭时触发 |
| Slot | default | — | 主内容区(放 BubbleList 等) |
| Slot | title | — | 自定义标题栏内容 |
| Slot | operations | — | 标题栏右侧操作区(在全屏/关闭按钮之前) |
| Slot | footer | — | 底部区域(放 Sender 等) |
主题与换肤:Container 的 CSS 变量体系与 OpenTiny Design 无缝对接
Container 的样式完全通过 CSS 变量控制,分为以下两类:
不影响布局的变量(颜色、字重等)
| CSS 变量 | 默认值(亮色) | 说明 |
|---|---|---|
--tr-container-bg-color | var(--tr-page-bg-default) → #f5f5f5 | 面板背景色 |
--tr-container-border-color | var(--tr-border-color-disabled) → #c2c2c2 | 边框颜色 |
--tr-container-title-color | var(--tr-text-primary) → #191919 | 标题文字颜色 |
--tr-container-title-font-weight | 600 | 标题字重 |
影响布局的变量(宽度、间距等)
| CSS 变量 | 默认值 | 说明 |
|---|---|---|
--tr-container-width | 480px | 侧边栏模式宽度 |
--tr-container-border-width | 1px | 边框宽度 |
--tr-container-header-padding | 0 24px 16px | 标题栏内边距 |
--tr-container-header-operations-gap | 8px | 操作按钮间距 |
--tr-container-title-font-size | 14px | 标题字号 |
--tr-container-title-line-height | 22px | 标题行高 |
全屏模式覆盖变量
| CSS 变量 | 默认值 | 说明 |
|---|---|---|
--tr-container-title-font-size-fullscreen | 16px | 全屏时标题字号 |
--tr-container-title-line-height-fullscreen | 22px | 全屏时标题行高 |
--tr-container-header-padding-fullscreen | 0 160px 16px | 全屏时标题栏内边距(居中效果) |
深色主题适配
切换到深色主题时,Container 的背景色和边框色会跟随全局变量自动变化:
--tr-page-bg-default:#f5f5f5→#191919--tr-border-color-disabled:#c2c2c2→#808080--tr-text-primary:#191919→#e6e6e6
您只需在根节点设置 data-tr-color-mode="dark" 或使用 ThemeProvider 组件,Container 的样式便会自动切换,无需额外配置。

与 OpenTiny Design Token 的映射关系
Container 的 CSS 变量并非凭空定义,而是映射到 TinyRobot 全局 Design Token:
| Container 变量 | 全局 Token | 语义 |
|---|---|---|
--tr-container-bg-color | --tr-page-bg-default | 页面级背景色 |
--tr-container-border-color | --tr-border-color-disabled | 禁用态边框色 |
--tr-container-title-color | --tr-text-primary | 主文本色 |
这种映射意味着:修改全局 Token,所有组件同步变化;只改 Container 变量,则仅影响 Container 自身。两层控制粒度,可按需灵活选择。
组件组合实战:Container + BubbleList + Sender + History 的最佳搭配
最小完整对话单元:Container + BubbleList + Sender
这是最常见的组合方式,覆盖 80% 的对话场景:
复制代码
带会话列表:Container + History
当需要会话管理功能(历史会话列表、新建会话、重命名等)时,可使用 History 组件:
复制代码
History 的 data 属性支持平铺数组或分组结构,menuItems 可配置右键菜单操作。
统一渲染策略:Container + BubbleProvider
当对话中需要渲染多种内容类型(文本、代码、图片、工具调用结果等)时,可使用 BubbleProvider 统一注册渲染器:
复制代码
四组件协作关系图
插槽嵌套顺序与样式隔离注意事项
- 默认插槽内容会被
.tr-container__header + *选择器赋予flex: 1; overflow-y: auto——这意味着您放在默认插槽中的第一个元素会自动成为可滚动的消息区域 #footer插槽的内容具有flex-shrink: 0——不会被内容区挤压,始终保持完整高度- Container 使用
scoped样式,子组件的样式不会泄漏到 Container 外部;但如果您在子组件中使用了全局 CSS 变量,这些变量仍然会生效
高级玩法:全屏模式、命名主题、多实例共存与自定义扩展
全屏模式:v-model:fullscreen
复制代码
...
源码中的切换逻辑:
复制代码const fullscreen = defineModel<boolean>('fullscreen')
const fullscreenToggleIcon = computed(() =>
fullscreen.value ? IconExitFullScreen : IconEnterFullScreen
)
全屏模式的 CSS 变化:
复制代码.tr-container.fullscreen {
--left: 0; /* 从右侧偏移变为占满全屏 */
--width: unset; /* 取消固定宽度 */
}
侧边栏模式下,Container 宽度固定为 480px,靠右显示(left: unset; right: 0);全屏模式下,left 归零、width 解除约束,面板占满整个视口。标题栏的 padding 也会从 0 24px 16px 变为 0 160px 16px,使标题在全屏时视觉居中。
命名主题:多主题切换的工程实践
使用 ThemeProvider 组件实现命名主题切换:
复制代码
...
ThemeProvider 通过 data-tr-theme 属性和 CSS 变量覆盖实现主题切换,Container 的所有样式变量都会自动跟随。
多实例共存:z-index 管理建议
Container 使用 z-index: var(--tr-z-index-fixed)(默认值为 100)。若需要多个 Container 实例(如主对话 + 帮助面板),建议:
- 通过 CSS 变量覆盖不同实例的 z-index:
--tr-z-index-fixed: 100/200 - 或者使用
#operations插槽添加层级切换按钮 - 不建议直接修改全局
--tr-z-index-fixed,这会影响所有固定定位元素
自定义扩展:slots 与 scoped slots 的扩展点
| 扩展点 | 能力 | 建议 |
|---|---|---|
#title | 完全替换标题栏内容 | 适合添加搜索框、状态指示器 |
#operations | 在全屏/关闭按钮前插入操作按钮 | 适合添加设置、分享等按钮 |
#footer | 完全替换底部区域 | ️ 替换后需自行处理输入区布局 |
| CSS 变量覆盖 | 修改颜色、宽度、间距等 | 推荐优先使用 CSS 变量而非修改源码 |
| 直接修改源码 | 任意修改 | 不建议,升级时存在冲突风险 |
边界说明:Container 的 position: fixed 布局和 flex 结构不建议修改——这是它作为"面板壳"的核心设计。如果需要内联布局或非固定定位,建议不使用 Container,直接使用 BubbleList + Sender 自行组装。
总结:Container 的设计理念与未来方向
回顾全文,Container 的设计可以用四个关键词概括:
- 导演模式——自身不演(不渲染内容),只负责编排(显隐、布局、事件桥接)
- 能力分层——显隐控制 → 布局编排 → 事件桥接,每层独立,互不耦合
- 主题一致——CSS 变量全部映射到全局 Design Token,换肤零成本
- 生态协同——与 BubbleList、Sender、History、BubbleProvider 天然组合,各司其职
这种设计的代价是:Container 不适合需要深度定制布局的场景(如内联嵌入、非固定定位)。但这是有意为之的取舍——Container 解决的是 80% 的标准对话面板需求,剩余 20% 的定制场景,TinyRobot 的组件化设计让您可以自由组合 Bubble、Sender 等原子组件。
值得继续深入的主题:
- Bubble 深度解析:角色配置、分组策略、自定义渲染器
- Sender 高级用法:Tiptap 扩展、Template、Mention、Suggestion
- TinyRobot 全家桶实战:从 CLI 创建到自定义主题的完整流程
关于 OpenTiny NEXT
OpenTiny NEXT 是一套企业级智能前端开发解决方案,以生成式 UI 和 WebMCP 两大核心技术为基础,对现有的 TinyVue 组件库、TinyEngine 低代码引擎等产品进行智能化升级,构建出面向 Agent 应用的前端 NEXT-SDKs、AI Extension、TinyRobot 智能助手、GenUI 等新产品,实现 AI 理解用户意图并自主完成任务,加速企业应用的智能化改造进程。
