---
title: Release 发布控制台
description: 从 CI 构建到灰度放量、由人审批的发布控制台
category: TemplateData
status: beta
since: 0.6.0
tags: [template, release, pipeline, rollout, approval]
syncStatus: reviewed
verified: false
---

## 用法

### 灰度发布
:::TuffDemoWrapper{demo="TemplateReleaseDemo" code-lang="vue" description="流水线自动跑完后停在首次放量的审批闸门，等你点击。版本、校验值与签名均为示例数据。"}
---
code: |
  <script setup lang="ts">
  import { reactive, ref } from 'vue'
  import TemplateFrame from './TemplateFrame.vue'

  const steps = reactive({ build: 'completed', sign: 'active', notarize: 'wait', stage: 'wait', rollout: 'wait' })
  const gate = ref<'hidden' | 'waiting' | 'allowed' | 'denied'>('hidden')
  // 首次放量之前，目标就是闸门请求的 5%
  const rollout = reactive({ started: false, current: 0, target: 5, halted: false })

  // CI 各步按计划推进，到分发完成为止；之后不再安排任何计时器
  function run() {
    later(1000, () => Object.assign(steps, { sign: 'completed', notarize: 'active' }))
    // …公证、分发…
    later(4300, () => {
      steps.rollout = 'active'
      gate.value = 'waiting'
    })
  }

  // 只有读者点「开始灰度」才会走到这里
  function onApprove({ remember }: { remember: boolean }) {
    gate.value = 'allowed'
    autoHalt.value = remember // 复选框的文案改成了「护栏失守时自动暂停」
    Object.assign(rollout, { started: true, current: 5, target: 25 })
  }
  </script>

  <template>
    <TemplateFrame title="Release 发布控制台" :height="600" @enter="run">
      <template #default="{ width, height }">
        <header class="release__head">
          <TxVersionCapsule v-model:panel="panel" version="v2.5.0-demo.4" channel="DEMO">
            <template #download="{ close }">
              <!-- builds 不带 href：示例构建不提供下载 -->
              <TxVersionDownloadPanel :notice="demoNotice" :builds="builds" @select="close()" />
            </template>
          </TxVersionCapsule>
          <TxTag label="示例数据" variant="plain" />
        </header>

        <!-- :active="-1"：每一步的 status 全部受控，暂停时灰度这一步才能显示为 error -->
        <TxSteps size="small" :active="-1">
          <TxStep v-for="key in STEP_KEYS" :key="key" :title="titles[key]" :status="steps[key]" :clickable="false" />
        </TxSteps>

        <TxDataTable :columns="columns" :data="artifacts" :max-height="width >= 960 ? Math.round(height * 0.42) : undefined">
          <template #cell-sha="{ row }">
            <code>{{ row.hash.slice(0, 4) }}…{{ row.hash.slice(-4) }}</code>
            <TxCopyButton :text="`SAMPLE-${row.hash}`" copy-label="复制示例 sha256" />
          </template>
        </TxDataTable>
        <div class="release__log">
          <TxCodeStream :code="log" :revealed-lines="revealed" :min-height="0" />
        </div>

        <aside class="release__rail">
          <TxToolConfirmation
            v-if="gate === 'waiting'"
            tool-name="rollout.start" risk="execute"
            allow-label="开始灰度" deny-label="暂不发布" remember-label="护栏失守时自动暂停"
            @approve="onApprove" @deny="onDeny"
          />
          <TxTextMorph :text="rollout.current" />
          <TxSegmentedSlider v-model="rollout.target" :segments="[1, 5, 25, 50, 100].map(value => ({ value, label: `${value}%` }))" />
          <TxButton :disabled="!!promoteReason" @click="openModal('promote')">晋级到 {{ rollout.target }}%</TxButton>
          <TxSignalMeter v-for="row in guardrails" :key="row.key" :value="row.level" :tone="row.tone" :label="row.label" />
          <TxTimeline>
            <TxTimelineItem v-for="entry in timeline" :key="entry.id" :title="entry.text" :time="entry.time" :color="entry.color" />
          </TxTimeline>
        </aside>

        <TxModal v-model="modal.open" :title="modalTitle">…</TxModal>
      </template>
    </TemplateFrame>
  </template>
---
:::

## 概述

Tuff 桌面端的一次发布：流水线自动完成，凡是会推送给用户的动作都要你亲手确认。

- 进入视口后流水线约 4 秒跑完签名、公证与分发，停在首次放量的审批闸门，不设计时器；减少动态效果时直接显示到闸门为止的状态。
- 「开始灰度」后比例滚到 5%，护栏填好后才能晋级；「暂不发布」只分发到预发，可随时重新申请。
- 闸门里的复选框是「护栏失守时自动暂停」，这是页面上唯一的自动动作，且只往安全方向走。
- 晋级、暂停与恢复都要在确认框里再点一次；护栏未达标或目标不高于当前比例时，晋级按钮不可用并写明原因。
- 打开「模拟崩溃率下滑」后无崩溃会话跌破 99.50%：勾过自动暂停就立即暂停，否则挡住晋级。
- 版本胶囊的下载面板只提示「示例构建，不提供下载」，面板打开时 Esc 只关面板；复制的 sha256 带 `SAMPLE-` 前缀。
- 宽屏（≥ 960px）为三栏；列内（640–959px）用「日志 / 产物 / 说明」页签；窄屏（< 640px）把灰度作为第一个页签。

## 自定义

- 接真实 CI 时，把 `pipelinePlan()` 里的定时步骤换成构建系统的事件，每个事件更新一步状态并追加一行日志。
- 首次放量这类动作用 TxToolConfirmation 并让它无限等待；它的「记住」复选框无法隐藏，只给它往安全方向的含义。
- TxSteps 的 `active` 下标会盖过单步的 `status`：要显示「已暂停」这类错误态，传 `:active="-1"` 并自行控制每一步。
- 示例数据要一眼可辨：版本号用不会发布的预发布标识，校验值与签名标「示例」，TxVersionDownloadPanel 的构建不传 `href`。

## 相关组件

| 区域 | 组件 | 作用 |
|---|---|---|
| 顶栏 | [TxVersionCapsule](/docs/dev/components/version-capsule)、[TxTag](/docs/dev/components/tag)、[TxWorkingIndicator](/docs/dev/components/working-indicator)、[TxStatusBadge](/docs/dev/components/status-badge)、[TxButton](/docs/dev/components/button) | 示例版本、状态与暂停 |
| 流水线 | [TxSteps](/docs/dev/components/steps) | 受控状态的五步流水线 |
| 产物 | [TxDataTable](/docs/dev/components/data-table)、[TxCopyButton](/docs/dev/components/button)、[TxStatusBadge](/docs/dev/components/status-badge) | 五个产物与示例校验值 |
| 日志 | [TxCodeStream](/docs/dev/components/code-stream) | 逐行出现的构建日志 |
| 发布说明 | [TxMarkdownView](/docs/dev/components/markdown-view) | 随版本切换的说明 |
| 灰度 | [TxTextMorph](/docs/dev/components/text-morph)、[TxProgressBar](/docs/dev/components/progress-bar)、[TxSegmentedSlider](/docs/dev/components/segmented-slider)、[TxAlert](/docs/dev/components/alert) | 比例、目标档位与晋级 |
| 审批闸门 | [TxToolConfirmation](/docs/dev/components/tool-confirmation) | 首次放量前等你确认 |
| 护栏 | [TxSignalMeter](/docs/dev/components/signal-meter)、[TxSwitch](/docs/dev/components/switch)、[TxSparkChart](/docs/dev/components/spark-chart) | 三项护栏与模拟开关 |
| 本次动态 | [TxTimeline](/docs/dev/components/timeline) | 本次发布的动态 |
| 确认与反馈 | [TxModal](/docs/dev/components/modal)、[TxToastPanel](/docs/dev/components/toast-panel) | 二次确认与结果提示 |
