- 前端
- UI组件
- 小程序
- 设计系统
- 移动开发
【免费下载链接】tdesign-miniprogram
Wechat MiniProgram and Uniapp UI components lib for TDesign
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; },也就是说:
- 纯字符串 / 数字选项会被自动包装为
{ value, label: String(value) },即选项的展示文案默认就是其值本身; - 结构化选项未提供
label时,label回退为value的字符串形式(coalesce为取值工具函数,见 common/utils.js); - 图标经过
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 }) => voidvalue:新选中的选项值;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], }); },值得注意的两个细节:
- 点击当前已选中项不会触发 change:源码中
if (index === activeIndex) return;保证了重复点击不产生无效事件; - 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);`; } }); },该方法的实现要点:
- 测量:通过
uni.createSelectorQuery封装的getRect(见 common/utils.js)同时获取当前激活选项与__group容器的位置矩形; - 计算:滑块宽度取激活选项的
width,横向位移取激活选项左边缘相对容器左边缘的偏移量itemRect.left - groupRect.left; - 动画:滑块通过
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
相关推荐
tdesign-uniapp Stepper 步进器组件:从 Props 配置到源码实现全解析
tdesign uniapp Stepper 步进器组件:从 Props 配置到源码实现全解析 本文是一份围绕 TDesign 开源组件库中 uniapp 版
前端UI组件小程序设计系统移动开发tdesign-miniprogram Segmented 分段控制器:从接入、受控值到源码级原理全解析
tdesign miniprogram Segmented 分段控制器:从接入、受控值到源码级原理全解析 本文以 Segmented 分段控制器官方文档 htt
前端UI组件小程序设计系统移动开发TDesign UniApp CountDown 倒计时组件实战:从参数配置到计时引擎源码解析
TDesign UniApp CountDown 倒计时组件实战:从参数配置到计时引擎源码解析 CountDown 是 TDesign UniApp( @tde
前端UI组件小程序设计系统移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考