---
title: Transfer 穿梭框
description: 在两个列表间移动条目的穿梭框
category: Data
status: beta
since: 0.3.4
tags: [transfer, data, permission]
syncStatus: reviewed
verified: true
---

## 用法

### 可筛选穿梭
`filterable` 在两侧显示筛选输入；`emptyText` 本地化空态文案。
:::TuffDemoWrapper{demo="TransferTransferDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const selected = ref<Array<string | number>>(['release'])
  const data = [
    { key: 'release', label: '正式通道' },
    { key: 'beta', label: 'Beta 通道' },
    { key: 'snapshot', label: '快照通道' },
    { key: 'internal', label: '内部灰度', disabled: true },
  ]
  </script>

  <template>
    <TxTransfer
      v-model="selected"
      :data="data"
      :titles="['可选渠道', '已选择']"
      filterable
      filter-placeholder="筛选渠道"
      empty-text="暂无数据"
      add-aria-label="添加选中渠道"
      remove-aria-label="移除选中渠道"
    />
  </template>
---
:::

### 优先级排序
`orderable` 让目标面板按 `modelValue` 顺序显示名次与上移 / 下移；`maxHeight` 让长列表在面板内滚动。
:::TuffDemoWrapper{demo="TransferOrderableDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const selected = ref<Array<string | number>>(['gpt-1', 'gpt-3', 'gpt-2'])
  const data = [
    { key: 'gpt-1', label: '主模型 · 高质量' },
    { key: 'gpt-2', label: '备用模型 · 均衡' },
    { key: 'gpt-3', label: '快速模型 · 低延迟' },
    { key: 'gpt-4', label: '长上下文模型' },
  ]
  </script>

  <template>
    <TxTransfer
      v-model="selected"
      :data="data"
      :titles="['可用模型', '调用顺序']"
      :empty-text="['没有更多模型', '还没有排序，先加一个模型']"
      :max-height="260"
      filterable
      orderable
      target-order="push"
      move-up-aria-label="上移一位"
      move-down-aria-label="下移一位"
    />
  </template>
---
:::

### 权限资源授权
`TxTransfer` 只负责资源的受控移动；权限域、归属团队与审计分别交给 `TxTree`、`TxTreeSelect` 与 `TxTimeline`。
:::TuffDemoWrapper{demo="ComponentsPermissionOrchestrationDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const assignedKeys = ref<Array<string | number>>(['read-docs', 'publish-notes'])
  const resources = [
    { key: 'read-docs', label: '读取文档' },
    { key: 'publish-notes', label: '发布日志' },
    { key: 'manage-assets', label: '管理资源' },
    { key: 'rotate-keys', label: '轮换密钥', disabled: true },
    { key: 'view-billing', label: '查看账单' },
  ]
  </script>

  <template>
    <TxTransfer
      v-model="assignedKeys"
      :data="resources"
      :titles="['可分配资源', '已授权']"
      filterable
      filter-placeholder="筛选资源"
      empty-text="暂无资源"
      add-aria-label="添加选中资源"
      remove-aria-label="移除选中资源"
    />
  </template>
---
:::

### 最佳实践

- `modelValue` 由业务侧受控，只保存条目 key；每个 `data[].key` 保持唯一且稳定。
- 禁用项保留在 `data` 中展示，不在外层过滤掉，以免用户误以为资源不存在。
- 选择顺序有意义时用 `targetOrder="push"`；需要用户手动排序（优先级、回退链）时开 `orderable`。
- 放进弹窗或抽屉时用 `maxHeight` 贴合容器（如 `min(56dvh, 520px)`）；紧凑位置调低 `minHeight`。
- 本地化界面覆盖 `titles`、`emptyText` 与全部 `*AriaLabel` 属性。

## API 参考

### 属性

::TuffPropsTable
---
rows:
  - name: modelValue
    type: "Array<string | number>"
    default: "[]"
    description: 目标列表中的条目 key。
  - name: data
    type: TransferItem[]
    default: "[]"
    description: 全量条目，含 key、label 与可选的 disabled。
  - name: titles
    type: "[string, string]"
    default: "['Source', 'Target']"
    description: 左右面板标题。
  - name: filterable
    type: boolean
    default: false
    description: 两侧显示筛选输入。
  - name: filterPlaceholder
    type: string
    default: "''"
    description: 筛选输入的占位文案。
  - name: emptyText
    type: "string | [string, string]"
    default: "'No data'"
    description: 面板为空时的文案；传元组则左右各用一条。
  - name: maxHeight
    type: "string | number"
    default: "'320px'"
    description: 面板高度上限，数字按 px；写入 --tx-transfer-max-height。
  - name: minHeight
    type: "string | number"
    default: "'240px'"
    description: 面板高度下限，数字按 px；写入 --tx-transfer-min-height。
  - name: addAriaLabel
    type: string
    default: "'Move selected items to target'"
    description: 添加按钮的可访问名称。
  - name: removeAriaLabel
    type: string
    default: "'Move selected items to source'"
    description: 移除按钮的可访问名称。
  - name: selectAllAriaLabel
    type: string
    default: "'Select all'"
    description: 全选框的可访问名称，后接面板标题。
  - name: moveUpAriaLabel
    type: string
    default: "'Move item up'"
    description: 上移按钮的可访问名称，后接条目文本。
  - name: moveDownAriaLabel
    type: string
    default: "'Move item down'"
    description: 下移按钮的可访问名称，后接条目文本。
  - name: targetOrder
    type: "'original' | 'push'"
    default: "'original'"
    description: 目标列表按原始 data 顺序或追加顺序排列；`orderable` 时不生效。
  - name: orderable
    type: boolean
    default: false
    description: 目标面板显示名次与上移/下移，顺序即 modelValue 顺序。
---
::

### 事件

| 事件 | 参数 | 说明 |
|------|------|------|
| `update:modelValue` | `Array<string \| number>` | 已选 key 更新。 |
| `change` | `Array<string \| number>` | 与 `update:modelValue` 同时触发。 |

## 概述

- 面板头部的全选框只作用于当前可见且未禁用的行；部分选中时报告 `aria-checked="mixed"`。
- 勾选后按中间按钮移动，或直接双击一行；双击禁用行无效。
- 中间按钮有勾选时变为主色并显示待移动数量，无勾选时禁用。
- `orderable` 时名次与首末判定取自未过滤的 `modelValue`，筛选不影响名次。

## 技术实现

- 面板读取 `--tx-transfer-max-height`（默认 `320px`）与 `--tx-transfer-min-height`（默认 `240px`），两个 prop 只写这两个变量；上限保证滚动发生在列表内部。
- 源码：`packages/tuffex/packages/components/src/transfer/`。

<TuffDocSourceLink label="查看源码" />
