NutUI 移动端 Collapse 折叠面板实战指南:从安装、数据绑定到手风琴与主题定制
NutUI 移动端 Collapse 折叠面板实战指南:从安装、数据绑定到手风琴与主题定制
Collapse(折叠面板)是 NutUI 移动端 Vue 组件库中用于收纳与展示内容的经典组件:将内容放置于多个面板中,点击面板标题即可展开或收缩内容,适合设置项、FAQ、分组信息等高频场景。本文以 @nutui/nutui-taro 中 Collapse 组件(doc.taro.md)为骨架,结合 collapse.vue、collapse-item.vue 等源码,系统讲解安装方式、v-model 数据绑定、手风琴模式、自定义图标、禁用状态、固定内容(extra)插槽以及主题定制,读完即可在 H5 与 Taro 小程序多端项目中熟练落地使用。
介绍
Collapse 折叠面板的核心交互非常简单:将内容放置在多个折叠面板中,点击面板标题可展开或收缩内容。它通常由一对父子组件配合使用:
NutCollapse:外层容器,负责管理展开状态(v-model)与手风琴开关(accordion);NutCollapseItem:单个面板,通过name属性作为唯一标识符,承载标题(title)、描述(label)、右侧说明(value)、自定义图标(icon)与面板内容。
该组件在 NutUI 仓库中位于 src/packages/__VUE/collapse,同时提供 H5 版本(collapse.vue)与 Taro 多端版本(collapse.taro.vue),两版实现逻辑保持一致,均以 view 标签承载模板,确保在微信小程序等环境可正常渲染。
安装
在 Taro 项目中,从 @nutui/nutui-taro 引入 Collapse 与 CollapseItem 两个组件并注册到应用即可:
import { createApp } from 'vue'
import { Collapse, CollapseItem } from '@nutui/nutui-taro'
const app = createApp()
app.use(Collapse)
app.use(CollapseItem)
从源码看,两个组件都通过 withInstall 完成挂载式安装:H5 入口在 index.ts,Taro 入口在 index.taro.ts,二者除了引入的 .vue 文件不同(collapse.vue / collapse.taro.vue)外,导出结构完全一致。CollapseItem 的实现在独立的 collapseitem 目录中,使用时按需引入即可。
基础用法
使用折叠面板时,CollapseItem 组件必须设置 name 属性作为唯一标识符,用于区分各个面板,同时作为 v-model 绑定值的内容来源。最简单的示例如下(对应仓库示例 demo/basic.vue):
<template>
<nut-collapse>
<nut-collapse-item name="name1" title="Title 1"> Content 1 </nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2" value="Description"> Content 2 </nut-collapse-item>
<nut-collapse-item name="name3">
<template #title> Title 3 </template>
<template #value> Description </template>
Content 3
</nut-collapse-item>
</nut-collapse>
</template>
模板结构上,每个 nut-collapse-item 的渲染可分为三部分(见 collapse-item.vue 模板):
- 标题栏(
nut-collapse-item__title):点击该区域触发展开/收缩,包含左侧标题与可选 label、右侧 value 与旋转图标; - extra 区域(
nut-collapse-item__extraWrapper):当存在extra插槽时渲染,用于放置不折叠的固定内容; - 内容容器(
nut-collapse-item__wrapper):包裹默认插槽内容,通过动态切换height(0px↔ 实际内容高度)配合transition: height 0.3s ease-in-out实现平滑的展开收起动画。
绑定数据与事件
通过 v-model 可以控制展开的 CollapseItem,默认情况下为数组类型,其中的值是对应 CollapseItem 的 name 属性:
<template>
<nut-collapse v-model="val" @change="change">
<nut-collapse-item name="name1" title="Title 1"> Content 1 </nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2"> Content 2 </nut-collapse-item>
<nut-collapse-item name="name3" title="Title 3"> Content 3 </nut-collapse-item>
</nut-collapse>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const val = ref(['name2', 'name3'])
const change = (modelValue: string[], name: string, status: boolean) => {
console.log(modelValue, name, status)
}
</script>
对应的完整示例见 demo/data.vue。这里 val 初始为 ['name2', 'name3'],即默认展开 name 为 name2、name3 的两个面板。
从底层实现看(collapse.vue),容器组件维护一个内部状态 innerValue,并监听外部 modelValue 的变化同步更新;当某个面板被点击时,updateVal 会依据当前 v-model 计算新的值数组——已展开则移除该 name(过滤),未展开则追加该 name——随后通过 changeVal 依次触发 update:modelValue(驱动 v-model 双向更新)与 change 事件:
const changeVal = (val: string | number | Array<string | number>, name: string | number, status = true) => {
innerValue.value = val
emit('update:modelValue', val)
emit('change', val, name, status)
}
change 事件回调参数:(modelValue, currName, status),分别表示最新展开集合、当前被点击面板的 name、以及本次操作是展开(true)还是收起(false)。这一点在 collapse.spec.tsx 的测试用例中得到了验证:当未传 v-model 时点击第二个面板,断言 onChange 收到 ([222222], 222222, true);再次点击则收到 ([], 222222, false)。
注意:非手风琴模式下若 v-model 不是数组,源码中会输出
[NutUI] <Collapse> 未开启手风琴模式时 v-model 应为数组的警告,请务必使用数组作为绑定值。
手风琴
通过 accordion 可以设置为手风琴模式,最多展开一个面板,此时 v-model 为单个 name(string 或 number):
<template>
<nut-collapse v-model="val" accordion @change="change">
<nut-collapse-item name="name1" title="Title 1"> Content 1 </nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2"> Content 2 </nut-collapse-item>
<nut-collapse-item name="name3" title="Title 3"> Content 3 </nut-collapse-item>
</nut-collapse>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const val = ref('name2') // 手风琴模式下为单个 name
const change = (modelValue: string, name: string, status: boolean) => {
console.log(modelValue, name, status)
}
</script>
示例源码见 demo/accordion.vue。手风琴模式的展开判断逻辑集中在 collapse.vue 的 updateVal 与 isExpanded 中:
isExpanded:手风琴模式下直接用innerValue === name判断是否展开;非手风琴模式则判断 name 是否包含在数组中;updateVal:手风琴模式下点击当前已展开面板会收起(置空),点击其他面板则切换为该 name,天然保证"最多展开一个"。
测试 collapse.spec.tsx 中也专门覆盖了该场景:modelValue={2} accordion 时仅 1 个图标处于展开态,点击第 1 个面板后依然只有 1 个展开。
自定义折叠图标
通过 slots.icon 或者 props.icon 属性均可设置自定义图标。icon 属性等同于 nutui-icon 组件,可从 @nutui/icons-vue 引入任意图标:
<template>
<nut-collapse>
<nut-collapse-item name="name1" title="Title 1">
<template #icon> <Notice /> </template>
Content 1
</nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2" :icon="Follow"> Content 2 </nut-collapse-item>
</nut-collapse>
</template>
<script setup lang="ts">
import { Notice, Follow } from '@nutui/icons-vue'
</script>
完整示例见 demo/custom.vue。渲染优先级上,插槽优先于 props:在 collapse-item.vue 中,图标区域先判断是否存在 $slots.icon,存在则渲染插槽内容,否则通过 renderIcon 渲染 props.icon 传入的图标组件(默认值为 DownArrow 下箭头)。
需要注意两点:
- rotate 参数:
rotate(默认180)控制图标在折叠与展开之间的旋转角度,仅在自定义图标模式下生效。源码中图标元素通过transform: rotate(...)动态应用角度:展开时旋转rotate度,收起时为0度; - 展开状态类名:展开时图标容器会追加
nut-collapse-item__title-icon--expanded类,index.scss 中为其定义了rotate(-180deg)的默认旋转样式(与 props 默认值180呼应)。
禁用状态
为 CollapseItem 设置 disabled 属性即可禁用标题栏,此时该面板无法被点击展开/收起:
<template>
<nut-collapse>
<nut-collapse-item name="name1" title="Title 1"> Content 1 </nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2" disabled> Content 2 </nut-collapse-item>
<nut-collapse-item name="name3" title="Title 3" disabled> Content 3 </nut-collapse-item>
</nut-collapse>
</template>
示例见 demo/disabled.vue。禁用状态的实现包含模板与样式两层:
- 模板层:标题栏绑定类名
nut-collapse-item__title--disabled,同时点击事件toggle中只有parent && parent.updateVal(props.name)一处调用,disabled并不参与点击拦截,因此必须依赖样式层阻止交互; - 样式层:index.scss 中为该类设置了
pointer-events: none与cursor: not-allowed,并叠加禁用色变量$collapse-item-disabled-color(默认#c8c9cc)。
测试 collapse.spec.tsx 中断言 nut-collapse-item__title--disabled 类会被正确添加,可据此验证禁用行为。
设置固定内容(不折叠)
通过 extra 插槽可以设置标题下方的固定内容,该区域始终渲染、不随面板折叠而隐藏,适合放置常驻操作按钮、提示信息等:
<template>
<nut-collapse>
<nut-collapse-item name="name1" title="Title 1">
<template #extra> Extra Content </template>
Content 1
</nut-collapse-item>
<nut-collapse-item name="name2" title="Title 2"> Content 2 </nut-collapse-item>
</nut-collapse>
</template>
示例见 demo/extra.vue。从模板结构看,extra 区域(nut-collapse__item-extraWrapper)与可折叠内容容器(nut-collapse__item-wrapper)是并列的两个节点:extra 容器的样式固定为 height: auto,不参与高度动画;而内容容器的高度由 0px 到实际内容高度的过渡驱动。展开/收起动画的测量逻辑在 collapse-item.vue 的 open/close 方法中:先测量 contentRef 的实际 offsetHeight,再通过双层 requestAnimationFrame 完成高度切换,过渡结束后将高度置为 auto,从而保证内容高度变化时仍能正确显示。
API
Collapse Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| v-model | 当前展开面板的 name |
手风琴模式:string | number 非手风琴模式:(string | number)[] |
- |
| accordion | 是否开启手风琴模式 | boolean | false |
类型定义上,CollapseProps 为 modelValue: CollapseValue 与 accordion: boolean 的 Partial 组合(collapse.vue),其中 CollapseValue = string | number | (string | number)<a href="https://link.gitcode.com/i/3b2b441da7fd811160f25a07d1b3edba" target="_blank">]([types.ts)。源码中 accordion 默认值为 false;modelValue 缺省时,内部会根据是否手风琴初始化默认值(非手风琴为 [],手风琴为 '')。
CollapseItem Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| name | 唯一标识符,必填 | string | number | -1 |
| title | 标题栏左侧内容,支持插槽传入(props 传入的优先级更高) |
string | - |
| value | 标题栏右侧内容,支持插槽传入(props 传入的优先级更高) |
string | - |
| icon | 标题栏左侧图标组件,等同于 nutui-icon 组件 |
- | - |
| label | 标题栏描述信息 | number | string | - |
| rotate | 点击折叠和展开的旋转角度,在自定义图标模式下生效 | string | number | 180 |
| disabled | 标题栏是否禁用 | boolean | false |
| border | 是否显示边框 | boolean | true |
源码(collapse-item.vue)中的默认值补充:name 默认 -1、rotate 默认 180、border 默认 true、icon 默认指向 DownArrow 下箭头图标。border 控制的是标题栏底部分隔线:开启时添加 nut-collapse-item__border 类,样式层通过 ::after 伪元素绘制 1px 边框(transform: scaleY(0.5) 实现高清屏细线),关闭时使用 collapse-border-none 类隐藏(index.scss)。
CollapseItem Slots
| 名称 | 说明 |
|---|---|
| title | 标题栏左侧内容插槽 |
| value | 标题栏右侧内容插槽 |
| extra | 设置标题下固定内容(不折叠) |
icon 4.2.0 |
设置自定义图标 |
模板渲染顺序上(collapse-item.vue):标题栏左侧优先渲染 title 插槽,无插槽时以 v-html 渲染 title 文本,其后追加 label 描述;右侧同理优先渲染 value 插槽,否则渲染 value 文本;label 与 value 均以 nut-collapse-item__title-label、nut-collapse-item__title-sub 类名定位样式。
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| change | 切换面板时触发 | (modelValue, currName, status) |
其中 modelValue 为最新展开集合(手风琴模式为单个 name,非手风琴模式为数组),currName 为本次点击面板的 name,status 为布尔值表示展开(true)/收起(false)。此外容器还会触发 update:modelValue 事件以支撑 v-model 双向绑定(collapse.vue)。
类型定义 v4.3.1
组件导出以下类型定义,便于在 TypeScript 项目中获得完整的类型推导:
import type {
CollapseValue,
CollapseProps,
CollapseInstance,
CollapseItemProps,
CollapseItemInstance
} from '@nutui/nutui-taro';
其中 CollapseProps、CollapseValue、CollapseInstance 由 index.taro.ts 导出,CollapseItemProps、CollapseItemInstance 由 collapseitem 目录对应入口导出。CollapseInstance/CollapseItemInstance 定义为 ComponentPublicInstance & InstanceType<typeof Component> 的交叉类型,可直接用于模板 ref 的实例类型标注。
父子通信原理:provide / inject
Collapse 的展开状态管理依赖 Vue 3 的 provide / inject 机制,这是理解其数据流的关键。容器 collapse.vue 通过 provide 向下注入一个名为 COLLAPSE_KEY 的上下文对象:
export const COLLAPSE_KEY = Symbol('nut-collapse') as InjectionKey<{
updateVal: (name: string | number) => void
isExpanded: (name: string | number) => boolean
}>
(见 types.ts)
子组件 collapse-item.vue 则通过 inject(COLLAPSE_KEY) 获取该上下文:点击标题时调用 updateVal(props.name) 上报自己的 name;渲染时调用 isExpanded(props.name) 判断自身是否展开,进而控制内容容器高度与图标旋转。这样每个 CollapseItem 无需感知兄弟面板的存在,展开规则完全由父容器统一裁决,父子解耦、扩展手风琴等新模式只需改动容器逻辑即可。
主题定制
样式变量
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件。这些变量在 styles/variables.scss 中统一声明,均以 !default 形式提供默认值,可在应用中通过 CSS 自定义属性覆盖:
| 名称 | 默认值 |
|---|---|
| --nut-collapse-item-padding | 13px 36px 13px 26px |
| --nut-collapse-item-color | #666666 |
| --nut-collapse-item-disabled-color | #c8c9cc |
| --nut-collapse-item-icon-color | #666666 |
| --nut-collapse-item-font-size | var(--nut-font-size-2) |
| --nut-collapse-item-line-height | 24px |
| --nut-collapse-item-sub-title-color | #666666 |
| --nut-collapse-wrapper-content-padding | 12px 26px |
| --nut-collapse-wrapper-empty-content-padding | 0 26px |
| --nut-collapse-wrapper-content-color | #666666 |
| --nut-collapse-wrapper-content-font-size | var(--nut-font-size-2) |
| --nut-collapse-wrapper-content-line-height | 1.5 |
| --nut-collapse-wrapper-content-background-color | var(--nut-white) |
典型的使用方式是在 ConfigProvider 或页面根节点上覆盖变量,例如调整面板内容区的内边距与文字颜色:
:root {
--nut-collapse-wrapper-content-padding: 16px 32px;
--nut-collapse-wrapper-content-color: #333333;
}
此外 collapseitem/index.scss 还内置了深色主题适配:在 .nut-theme-dark 作用域下,标题栏、内容区与 extra 区域会自动切换为深色背景与前景色,跟随 NutUI 整体暗黑模式配置生效。
小结
Collapse 折叠面板围绕"容器统一管理状态 + 面板上报自身 name"的 provide/inject 架构展开,v-model 数组(多选)与手风琴(单选)两种模式覆盖了绝大多数业务形态,title/value/icon/extra 四类插槽加上 rotate、disabled、border 等属性则提供了灵活的定制能力。借助文档中列出的 13 个 CSS 变量,还可以在不侵入源码的前提下完成整体视觉风格调整。相关示例与测试分别位于 demo 目录与 collapse.spec.tsx,可直接作为组件库内联演示与回归验证的参考。