---
title: Inbox 收件箱
description: 带键盘快捷键与新邮件提示的三栏收件箱
category: TemplateContent
status: beta
since: 0.6.0
tags: [template, inbox, list, reader, keyboard]
syncStatus: reviewed
verified: false
---

## 用法

### 三栏收件箱
:::TuffDemoWrapper{demo="TemplateInboxDemo" code-lang="vue" description="点一下列表后可用 J / K 切换、E 归档、S 加星标、Enter 回复。图中为示例数据。"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  // 快捷键挂在模板根上，不挂 document；焦点在输入框里时让出单字母键
  function onKeydown(event: KeyboardEvent) {
    if (event.defaultPrevented || isEditable(event.target))
      return
    if (event.key === 'j' || event.key === 'ArrowDown')
      move(1)
    else if (event.key === 'e')
      archive(selected.value)
  }
  </script>

  <template>
    <TemplateFrame title="Inbox" :height="580" @enter="startArrivals">
      <div class="inbox" @keydown="onKeydown">
        <TxSidebarNav v-if="wide" v-model="view" :items="navItems" :groups="navGroups" action-label="写邮件" />
        <TxFilterChips v-else v-model="view" :items="folderChips" aria-label="邮箱" />

        <TxSplitter v-model="split" :min="0.3" :max="0.62">
          <template #a>
            <!-- TxVirtualList 不带语义：listbox、option 与位置信息由宿主补上 -->
            <div role="listbox" tabindex="0" aria-label="收件箱" :aria-activedescendant="activeId">
              <TxVirtualList ref="list" :items="mails" :item-height="76" item-key="id" height="100%">
                <template #item="{ item, index }">
                  <div
                    :id="item.id"
                    role="option"
                    :aria-selected="item.id === selectedId"
                    :aria-setsize="mails.length"
                    :aria-posinset="index + 1"
                    @click="select(item.id)"
                  >
                    …
                  </div>
                </template>
              </TxVirtualList>
            </div>
          </template>

          <template #b>
            <TxMessageActions :copy-text="plainBody" :appear="false">…</TxMessageActions>
            <TxMarkdownView :content="selected.body" />
            <TxAttachmentTray :attachments="selected.attachments" :size-formatter="formatSize" />
            <TxChatComposer v-model="reply" placeholder="回复…" @send="send" @attachment-add="addFiles" />
          </template>
        </TxSplitter>
      </div>
    </TemplateFrame>
  </template>

  <style scoped>
  .inbox { display: grid; grid-template-columns: minmax(0, 1fr); }

  @container template (min-width: 960px) {
    /* 侧栏用固定宽度：只有列表和阅读区跟着分隔条变化 */
    .inbox { grid-template-columns: 216px minmax(0, 1fr); }
  }
  </style>
---
:::

### 通知中心
按天分组、每条带就地快捷操作的单列信息流，适合短、量大、要尽快处理的通知。
:::TuffDemoWrapper{demo="TemplateInboxNotificationsDemo" code-lang="vue" description="点一下列表后可用 J / K 切换、E 切换已读、X 选择；快捷操作就地执行并可撤销。图中为示例数据。"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  // 勿扰期间新通知静默插入、只计数；安全通知在「可穿透」开启时照常提醒
  function deliver(notice: Notice) {
    notices.value.unshift(notice)
    if (dndActive.value && !(notice.urgent && dnd.bypass)) {
      silenced.value += 1
      return
    }
    showArrival(notice)
  }

  // 快捷操作只由读者点击触发：自动演示只负责「到达」，从不替你批准
  function runAction(notice: Notice, action: ActionId) {
    notice.unread = false
    notice.result = notice.results[action] ?? null
  }
  </script>

  <template>
    <TemplateFrame title="通知中心" :height="580" @enter="startArrivals">
      <TxModeChip
        :label="dndActive ? '勿扰中 · 至 11:30' : '通知已开启'"
        :icon="dndActive ? 'i-carbon-notification-off' : 'i-carbon-notification'"
        :tone="dndActive ? 'warning' : 'muted'"
        @click="toggleDnd"
      />
      <TxFilterChips v-model="kind" :items="kindItems" aria-label="按类型筛选" />

      <section v-for="group in groups" :key="group.key">
        <TxCardItem
          v-for="notice in group.items"
          :key="notice.id"
          role="article"
          clickable
          :tabindex="notice.id === tabStop ? 0 : -1"
          @click="markReadAndOpen(notice)"
        >
          <template #description>
            {{ notice.body }}
            <!-- 按钮的点击不能再冒泡成「标为已读并打开」 -->
            <span @click.stop>
              <TxStatusBadge v-if="notice.result" :text="notice.result.text" :status="notice.result.tone" />
              <template v-else>
                <TxButton v-for="action in notice.actions" :key="action" size="sm" @click="runAction(notice, action)">…</TxButton>
              </template>
            </span>
          </template>
        </TxCardItem>
      </section>

      <TxCollapse v-model="snoozeOpen">
        <TxCollapseItem name="snoozed" title="已延后 · 2">…</TxCollapseItem>
      </TxCollapse>
    </TemplateFrame>
  </template>
---
:::

## 概述

Tuff 账户的邮件与通知中心：几百封邮件可快速扫读、用键盘切换、就地回复，新邮件不打断阅读。

- 焦点在模板里时：↑ / ↓ 或 J / K 切换，E 归档，S 加星标，Enter 聚焦回复框；在输入框里时单字母键不生效。
- 进入视口后陆续到达三封新邮件；读者已往下滚时，新邮件插在视口上方，列表不跳回顶部；减少动态效果时三封一开始就在。
- 回复框里 Enter 换行、⌘ / Ctrl + Enter 发送；归档与「移动到」立即生效并可撤销。
- 展开后（≥ 960px）左侧为固定 216px 的导航栏；窄于 640px 时只显示列表，阅读区从右侧滑入，Esc 返回。
- 通知中心：快捷操作就地执行并可撤销；批准、拒绝、是我 / 不是我只由读者点击，自动演示从不替你点。
- 通知中心：J / K 移动，E 切换已读，X 选中并进入选择模式，Esc 退出；⋯ 菜单可稍后提醒或静音来源。
- 通知中心：勿扰期间新通知静默插入并计数，安全通知在「可穿透」开启时照常提醒；展开后右侧常驻通知设置。

## 自定义

- 数据量更大时保持固定行高（`item-height`），搜索与分页交给服务端；`aria-setsize` 与 `aria-posinset` 按真实总数填写。
- 正文来自外部时，保留 TxMarkdownView 的默认净化，并在宿主里拦截链接点击。
- 快捷键可以改键，但始终挂在组件根节点上，并在可编辑元素里让出单字母键。
- 附件上传由宿主负责：TxChatComposer 只交出 File 对象，object URL 的创建与回收都在宿主里。
- 通知中心：接真实推送时，用同一份勿扰与来源设置决定是否提醒；快捷操作以服务端结果为准，失败时保留原按钮。

## 相关组件

| 区域 | 组件 | 作用 |
|---|---|---|
| 导航（展开后） | [TxSidebarNav](/docs/dev/components/sidebar-nav)、[TxDotIndicator](/docs/dev/components/dot-indicator) | 邮箱、标签与未读数 |
| 文件夹（列内与窄屏） | [TxFilterChips](/docs/dev/components/filter-chips) | 同一份文件夹与未读数 |
| 顶栏 | [TxSearchInput](/docs/dev/components/search-input)、[TxButton](/docs/dev/components/button)、[TxKbd](/docs/dev/components/kbd) | 搜索、写邮件与快捷键提示 |
| 分栏 | [TxSplitter](/docs/dev/components/splitter) | 列表与阅读区之间的分隔 |
| 邮件列表 | [TxVirtualList](/docs/dev/components/virtual-list)、[TxAvatar](/docs/dev/components/avatar)、[TxDotIndicator](/docs/dev/components/dot-indicator)、[TxTag](/docs/dev/components/tag)、[TxIconButton](/docs/dev/components/button)、[TxBadge](/docs/dev/components/badge)、[TxDropdownMenu](/docs/dev/components/dropdown-menu)、[TxSkeleton](/docs/dev/components/skeleton) | 约 160 封邮件的虚拟列表 |
| 空态 | [TxSearchEmpty](/docs/dev/components/search-empty)、[TxEmptyState](/docs/dev/components/empty-state)、[TxNoSelection](/docs/dev/components/no-selection) | 无结果、空文件夹与未选中 |
| 阅读区 | [TxMessageActions](/docs/dev/components/message-actions)、[TxDropdownMenu](/docs/dev/components/dropdown-menu)、[TxMarkdownView](/docs/dev/components/markdown-view)、[TxAttachmentTray](/docs/dev/components/attachment-tray) | 正文、附件、往来与操作 |
| 回复 | [TxChatComposer](/docs/dev/components/chat-composer)、[TxAttachmentTray](/docs/dev/components/attachment-tray) | 快捷键发送与附件 |
| 通知 | [TxToastPanel](/docs/dev/components/toast-panel) | 「查看」与「撤销」提示 |
| 顶栏（通知中心） | [TxBadge](/docs/dev/components/badge)、[TxModeChip](/docs/dev/components/mode-chip)、[TxButton](/docs/dev/components/button)、[TxIconButton](/docs/dev/components/button)、[TxPopover](/docs/dev/components/popover) | 未读数、勿扰与选择模式 |
| 类型筛选（通知中心） | [TxFilterChips](/docs/dev/components/filter-chips)、[TuffSwitch](/docs/dev/components/switch) | 按类型筛选与仅未读 |
| 通知行（通知中心） | [TxCardItem](/docs/dev/components/card-item)、[TxAvatar](/docs/dev/components/avatar)、[TxIconChip](/docs/dev/components/icon-chip)、[TxDotIndicator](/docs/dev/components/dot-indicator)、[TxTag](/docs/dev/components/tag)、[TxStatusBadge](/docs/dev/components/status-badge) | 头像、未读点与就地操作 |
| 行菜单（通知中心） | [TxDropdownMenu / TxDropdownSubmenu](/docs/dev/components/dropdown-menu) | 稍后提醒、静音与删除 |
| 批量（通知中心） | [TxCheckbox](/docs/dev/components/checkbox)、[TxButton](/docs/dev/components/button) | 整组选择与批量栏 |
| 已延后（通知中心） | [TxCollapse / TxCollapseItem](/docs/dev/components/collapse) | 末尾折叠，可取消延后 |
| 通知设置（通知中心） | [TuffSwitch](/docs/dev/components/switch)、[TxSelect](/docs/dev/components/select)、[TxFlatRadio](/docs/dev/components/flat-radio)、[TxIconChip](/docs/dev/components/icon-chip)、[TxTooltip](/docs/dev/components/tooltip) | 勿扰时段与按来源接收 |
| 空态（通知中心） | [TxEmptyState](/docs/dev/components/empty-state) | 清除筛选与查看已延后 |
