☰
TDesign Uniapp Segmented 分段控制器组件实战:从引入配置到源码实现解析
2026/10/16 2:41:17 网站建设 项目流程
  • 前端
  • UI组件
  • 小程序
  • 设计系统
  • 移动开发

【免费下载链接】tdesign-miniprogram

Wechat MiniProgram and Uniapp UI components lib for TDesign

项目地址:https://gitcode.com/gh_mirrors/tde/tdesign-miniprogram
点击查看免费下载

Segmented(分段控制器)是 TDesign Uniapp 组件库中用于「展示多个选项并允许用户选择其中单个选项」的基础交互组件,广泛应用于筛选条件切换、视图模式切换、Tab 类分段导航等单选项互斥场景。本文以仓库内 segmented/README.md 为骨架,结合组件源码、类型定义、样式文件与示例页面,系统讲解该组件的引入方式、全部配置项、受控与非受控用法、禁用状态、自适应宽度、事件机制以及拇指滑块动画的底层实现原理,帮助你在一站式开发中快速落地并理解其内部机制。

组件定位与核心特性

从 README 的组件描述可以看出,Segmented 的核心交互模型是「多选项、单选、互斥」,与单选框(Radio)语义相近,但呈现形态为横向分段式控制器,视觉上更紧凑、操作路径更短。

组件名称t-segmented由前缀t与组件名segmented拼接而来(前缀定义见 common/config.js)。其核心能力包括:

  • 数据化配置选项:通过options属性一次性传入全部选项,支持字符串数组、数字数组或结构化对象数组;
  • 受控 / 非受控双模式:支持v-model:value语法糖,也支持default-value非受控用法;
  • 基础 / 自适应宽度两种形态:block属性控制是否撑满父元素宽度;
  • 整组禁用与单项禁用:既有控制器级disabled,也支持选项级disabled;
  • 图标选项:选项可配置 icon,与文字组合展示;
  • 滑动拇指动画:选中背景(thumb)会跟随点击选项平滑移动。

安装与引入方式

安装组件库

在项目根目录执行:

npm i @tdesign/uniapp

安装完成后,在main.ts中引入组件库样式(推荐 Less 版本,以 rpx 为单位):

import '@tdesign/uniapp/theme.less'; // 或引入编译后的 CSS:import '@tdesign/uniapp/theme.css';

推荐:easycom 自动引入

对于 Uniapp 项目,推荐使用 easycom 模式引入组件,配置后无需手动import即可在模板中直接使用<t-segmented />。在pages.json中添加如下配置:

CLI 模式(node_modules 下使用@tdesign/uniapp):

{ "easycom": { "custom": { "^t-(.*)": "@tdesign/uniapp/$1/$1.vue" } } }

HBuilderX 模式(uni_modules 下使用tdesign-uniapp):

{ "easycom": { "custom": { "^t-(.*)": "@/uni_modules/tdesign-uniapp/components/$1/$1.vue" } } }

配置完成后,模板中可直接书写<t-segmented :options="options" />。完整引入步骤见 TDesign Uniapp 快速开始文档。

手动引入

若不使用 easycom,可在<script>中手动导入(与示例 base/index.vue 的写法一致):

import TSegmented from '@tdesign/uniapp/segmented/segmented.vue';

随后在components中注册即可在模板中使用。注意组件库只提供按需导入方式,不支持全量导入(全量导入在小程序环境下存在兼容性问题)。

组件类型:基础与自适应宽度

基础用法

Segmented 最基本的使用方式是传入options选项数组并配合value/default-value控制选中项。参考示例 base/index.vue:

<template> <t-segmented :options="options" :default-value="0" /> </template> <script> export default { data() { return { options: [ { value: 0, label: '选项' }, { value: 1, label: '选项' }, { value: 2, label: '选项' }, ], }; }, }; </script>

基础形态下,组件宽度由其内容撑开,选项按内容宽度排列。

自适应宽度(block)

当需要让分段控制器占满父容器整行宽度、各选项均分空间时,设置block属性即可,参考示例 block/index.vue:

<template> <t-segmented block :options="options" :default-value="0" /> </template>

从样式实现 segmented.less 可以看到,block形态下根节点变为display: flex; width: 100%,每个选项设置flex: 1; min-width: 0,因此所有选项会等分父容器宽度;同时选项内部文字通过overflow: hidden; text-overflow: ellipsis在空间不足时自动省略,保证超长文案不撑破布局。

选项数据格式与 options 详解

options是组件的核心数据源,TS 类型为string[] | number[] | SegmentedItem[]。结构化选项SegmentedItem的完整定义见 type.ts:

export interface SegmentedItem { value: string | number; label?: string; icon?: string | object; disabled?: boolean; }
  • value:选项唯一标识,必填,类型为字符串或数字;
  • label:展示文案,缺省时组件会回退为value的字符串形式;
  • icon:选项图标,可为图标名字符串或图标配置对象;
  • disabled:单项禁用标记。

从组件源码 segmented.vue 的updateOptions方法可以看到数据归一化的过程:

updateOptions(options) { if (!options?.length) return; const segmentItems = options.map((option) => { if (typeof option === 'string' || typeof option === 'number') { return { value: option, label: String(option) }; } return { ...option, label: coalesce(option.label, String(option.value)), icon: option.icon ? calcIcon(option.icon) : null, }; }); this.segmentItems = segmentItems; },

也就是说:

  1. 纯字符串 / 数字选项会被自动包装为{ value, label: String(value) },即选项的展示文案默认就是其值本身;
  2. 结构化选项未提供label时,label回退为value的字符串形式(coalesce为取值工具函数,见 common/utils.js);
  3. 图标经过calcIcon归一化为统一的对象形态{ name: ... }。

该归一化逻辑通过watch监听options并immediate: true立即执行,因此 options 数据在运行时动态更新也会被自动处理。

受控与非受控:value 与 default-value

Segmented 同时支持受控与非受控两种选中值管理方式,这也是 README 中value与default-value两个属性并存的由来。

受控模式(v-model)

value为受控属性,支持语法糖v-model:value。受控模式下选中值由外部数据驱动,变化时需通过change事件回写,示例:

<template> <t-segmented :options="options" :value="current" @change="onChange" /> </template> <script> export default { data() { return { current: 1, options: ['微信', '支付宝', '银联'], }; }, methods: { onChange(e) { this.current = e.value; }, }, }; </script>

非受控模式(default-value)

default-value为非受控属性,只在初始化时生效,之后选中态由组件内部维护。源码中dataValue的初始化正是二者的优先级合并:

dataValue: coalesce(this.value, this.defaultValue),

即value优先于default-value。

底层实现:controlledProps

受控机制的底层来自uniComponent的controlledProps配置(见 segmented.vue):

controlledProps: [ { key: 'value', event: 'change', }, ],

该声明将value与change事件绑定为受控对,使组件自动支持v-model:value。同时源码中通过watch监听外部value变化并同步到内部dataValue,再触发updateActiveIndex更新选中态,形成完整闭环。相关受控基座逻辑可参考 common/src/control.js 中的useControl实现。

组件状态:控制器禁用态

整组禁用

控制器级disabled属性为 Boolean 类型,设置后整组不可点击。在模板中:

<t-segmented disabled :options="options" :default-value="0" />

单项禁用

除了整组禁用,还可以通过SegmentedItem.disabled对单个选项禁用,参考示例 disabled/index.vue:

<template> <t-segmented :options="options" :value="current" @change="onChange" /> </template> <script> export default { data() { return { current: 0, options: [ { value: 0, label: '选中' }, { value: 1, label: '默认' }, { value: 2, label: '禁用', disabled: true }, ], }; }, }; </script>

上述示例中第三个选项「禁用」不可点击。从模板渲染逻辑看,选项的禁用态由「整组 disabled 或 单项 item.disabled」共同决定:

['active', index === activeIndex], ['disabled', disabled || item.disabled],

样式层面(见 segmented.less),禁用项通过pointer-events: none屏蔽点击,并以--td-segmented-item-disabled-color对应的禁用文本色展示;交互层面,handleSelect中对禁用态做了二次拦截:

if (disabled || !item || item.disabled) return;

事件机制:change

change事件在选项值发生变化时触发,回调参数结构为:

(context: { value: string | number, selectedOption: SegmentedItem }) => void
  • value:新选中的选项值;
  • selectedOption:选中的原始选项对象(即options中对应的原始条目)。

事件触发路径见handleSelect(segmented.vue):

handleSelect(index) { const { segmentItems, activeIndex, disabled, options } = this; const item = segmentItems[index]; if (disabled || !item || item.disabled) return; if (index === activeIndex) return; this._trigger('change', { value: item.value, selectedOption: options[index], }); },

值得注意的两个细节:

  1. 点击当前已选中项不会触发 change:源码中if (index === activeIndex) return;保证了重复点击不产生无效事件;
  2. selectedOption 返回的是原始 options 条目:组件在_trigger时取的是options[index]而非归一化后的segmentItems[index],保证回调中拿到的是开发者传入的原始数据对象。

图标选项

当SegmentedItem.icon为字符串时,会被渲染为对应的 TDesign 图标(内部使用t-icon组件,calcIcon将其归一化为{ name: icon })。示例 base/index.vue 中展示了带图标选项的写法:

<template> <t-segmented :options="optionsWithIcon" :value="valueWithIcon" @change="onChangeWithIcon" /> </template> <script> export default { data() { return { valueWithIcon: 0, optionsWithIcon: [ { value: 0, label: '选项', icon: 'home' }, { value: 1, label: '选项', icon: 'home' }, { value: 2, label: '选项', icon: 'home' }, ], }; }, }; </script>

icon同时也支持对象形态(即string | object),可在需要自定义图标资源时传入图标配置对象。图标与文字在选项内部通过 flex 布局水平居中排列,图标与文字之间保留calc(@spacer / 2)的间距(见 segmented.less)。

样式定制:External Classes 与 CSS Variables

External Classes 外部样式类

组件暴露了三个外部样式类,用于在不侵入组件内部样式的条件下覆盖根节点与关键子节点样式:

| 类名 | 描述 | | -- | -- | | t-class | 根节点样式类 | | t-class-item | 列表子项样式类 | | t-class-thumb | 动画背景样式类 |

在组件源码中它们通过externalClasses声明注册(t为组件前缀,见 common/config.js):

externalClasses: [`${prefix}-class`, `${prefix}-class-thumb`, `${prefix}-class-item`],

使用方式是在页面或父组件的样式中编写同名类,例如:

<t-segmented class="t-class" :options="options" />
.t-class { border: 1rpx solid #dcdcdc; } .t-class-item { letter-spacing: 2rpx; } .t-class-thumb { box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); }

注意:t-class-thumb作用于选中项背后的滑块背景,其宽度与位移由组件内部动态计算,外部类只用于叠加额外视觉效果。

CSS Variables 样式变量

组件在 README.md 中声明了下列 CSS 变量,可在任意上层作用域(页面或 App)覆盖以完成主题定制:

| 变量名 | 默认值 | 描述 | | -- | -- | -- | | --td-font-size-m | 32rpx | 选项图标字号 | | --td-segmented-bg-color | @bg-color-component-disabled | 控制器底色 | | --td-segmented-item-active-bg | @bg-color-container | 选中项(thumb)背景色 | | --td-segmented-item-active-color | @brand-color | 选中项文字颜色 | | --td-segmented-item-color | @text-color-primary | 普通项文字颜色 | | --td-segmented-item-disabled-color | @text-color-disabled | 禁用项文字颜色 | | --td-segmented-item-label-font | @font-body-medium | 选项文字字体 | | --td-segmented-transition | all @anim-duration-base @anim-time-fn-easing | thumb 位移动画过渡 |

这些变量在样式实现 segmented.less 中通过var()的第二个参数绑定到设计令牌默认值,例如:

@segmented-bg-color: var(--td-segmented-bg-color, @bg-color-component-disabled); @segmented-item-active-color: var(--td-segmented-item-active-color, @brand-color); @segmented-transition: var(--td-segmented-transition, all @anim-duration-base @anim-time-fn-easing);

也就是说,所有颜色与动效默认值均来自 TDesign 设计令牌(如@bg-color-component-disabled、@brand-color、@anim-duration-base),未覆盖变量时自动跟随主题体系;覆盖变量时即可实现局部品牌化定制。示例性覆盖写法:

page { --td-segmented-bg-color: #f0f2f5; --td-segmented-item-active-color: #0052d9; }

源码实现原理:thumb 滑块的渲染与定位

Segmented 最具有代表性的交互是选中背景(thumb)的平滑滑动。从 segmented.vue 模板结构看,组件渲染层级为:

view.t-segmented └── view.t-segmented__group(相对定位容器) ├── view.t-segmented__thumb(绝对定位滑块背景,style=thumbStyle) └── view.t-segmented__item × N(每个选项,z-index: 1)

thumb 是一个绝对定位元素,其尺寸与位置完全由 JS 动态计算得出,核心是updateThumb方法:

updateThumb() { const { activeIndex, classPrefix } = this; if (activeIndex < 0) return; const groupClass = `.${classPrefix}__group`; const itemClass = `.${classPrefix}-item-${activeIndex}`; Promise.all([getRect(this, itemClass), getRect(this, groupClass)]).then(([itemRect, groupRect]) => { if (itemRect && groupRect) { const left = itemRect.left - groupRect.left; this.thumbStyle = `width: ${itemRect.width}px; transform: translateX(${left}px);`; } }); },

该方法的实现要点:

  1. 测量:通过uni.createSelectorQuery封装的getRect(见 common/utils.js)同时获取当前激活选项与__group容器的位置矩形;
  2. 计算:滑块宽度取激活选项的width,横向位移取激活选项左边缘相对容器左边缘的偏移量itemRect.left - groupRect.left;
  3. 动画:滑块通过transform: translateX移动,配合样式层transition: var(--td-segmented-transition)实现平滑滑动,而非生硬跳变。

updateThumb的触发时机包括:组件mounted后、dataValue变化导致激活索引更新后($nextTick中调用),以及segmentItems变化后,确保首次渲染、切换选中、选项数据变更三种场景下滑块位置都保持正确。若当前没有选中项(activeIndex < 0),则跳过测量,thumb 保持初始位置。

完整属性速查表

为便于查阅,汇总 README 中全部 Props 与说明:

| 属性 | 类型 | 默认值 | 说明 | | -- | -- | -- | -- | | custom-style | Object | - | 自定义样式 | | block | Boolean | false | 是否撑满父元素宽度 | | disabled | Boolean | - | 是否禁用 | | options | Object | [] | 数据化配置选项内容,string[] \| number[] \| SegmentedItem[]| | value | String / Number | - | 当前选中的值,支持v-model:value| | default-value | String / Number | - | 当前选中的值,非受控属性 |

事件:change,参数(context: { value: string \| number, selectedOption: SegmentedItem }),选项值发生变化时触发。

总结与最佳实践

结合 README 与源码,使用 Segmented 时有几个值得留意的实践要点:

  • 优先 easycom:配置一次pages.json的 easycom 规则即可全项目免 import 使用<t-segmented />,是最省事的接入方式;
  • 受控与业务状态联动:当选中值需要与页面其他区域联动(如切换筛选后刷新列表)时,使用:value+@change双向回写;仅需默认态时用default-value即可;
  • 善用单项禁用:在展示类场景(如会员等级、套餐档次)中,通过SegmentedItem.disabled表达「暂不可用」比整组禁用更贴近业务;
  • 选项图标:需要图标时记得options使用结构化SegmentedItem而非纯字符串数组;
  • 主题统一:颜色与动画默认跟随 TDesign 设计令牌,品牌定制只需覆盖 README 列出的 CSS 变量,无需侵入组件内部样式;
  • 注意重复点击:点击当前已选中项不会触发change,业务代码无需额外做去重判断。

如果你需要对照完整示例页面,可直接查看 Segmented 示例聚合页 及其下的 base、block、disabled 三个分示例;深入阅读实现可继续跟踪 segmented.vue、segmented.less 与 type.ts。

  • 前端
  • UI组件
  • 小程序
  • 设计系统
  • 移动开发

【免费下载链接】tdesign-miniprogram

Wechat MiniProgram and Uniapp UI components lib for TDesign

项目地址:https://gitcode.com/gh_mirrors/tde/tdesign-miniprogram
点击查看免费下载

相关推荐

上一篇:从零跑通 CPython:给新手的完整上手指南
下一篇:PostCSS Preset Env指南:如何使用未来CSS语法特性 🚀

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询