NutUI Collapse 折叠面板组件完全指南:v-model 双向绑定、手风琴模式与主题定制

原创2026-10-08 20:24:511,633 阅读
文章标签:前端UI组件

NutUI Collapse 折叠面板组件完全指南:v-model 双向绑定、手风琴模式与主题定制

折叠面板(Collapse)是移动端页面中最常见的交互组件之一:把内容收进多个可折叠的区块中,用户点击标题即可展开或收起,从而在有限屏幕内承载更多信息。本文以京东风格移动端 Vue 组件库 NutUI 中的 Collapse 组件为对象,完整讲解其安装方式、六类核心用法、完整 API 参数、事件回调与 CSS 变量主题定制方案,并结合仓库源码剖析其 provide/inject 通信与高度过渡动画的底层实现,帮助你直接照搬到真实项目中。

组件概述

Collapse 折叠面板由两个组件组成:

  • NutCollapse:外层容器,负责维护「当前展开面板」的状态,并通过 Vue 的 provide 向下传递控制方法;
  • NutCollapseItem:单个面板,负责渲染标题栏与内容区,并通过 inject 获取容器的状态来实现展开/收起。

官方对它的定位非常明确:将内容放置在多个折叠面板中,点击面板标题即可展开或收缩内容。在 NutUI 仓库中,H5 端实现位于 collapse.vue 与 collapse-item.vue,Taro 多端版本位于同目录的 .taro.vue 文件,二者逻辑完全一致,仅 DOM 渲染方式不同。

安装与引入

Collapse 和 CollapseItem 是两个独立组件,需要分别注册。NutUI 支持按需引入,示例代码如下:

import { createApp } from 'vue'
import { Collapse, CollapseItem } from '@nutui/nutui'

const app = createApp()
app.use(Collapse)
app.use(CollapseItem)

如果使用 Taro 多端小程序,则将包名替换为 @nutui/nutui-taro,引入方式完全一致(参见 doc.taro.md)。

从源码看,两个组件的入口文件都通过 withInstall 包装后再导出,因此既可以 app.use() 全局注册,也可以 import { Collapse } 后直接局部使用:

基础用法

使用折叠面板的前提是每个 CollapseItem 必须设置 name 属性作为唯一标识符,后续的 v-model 绑定、事件回调都依赖这个 name 来定位具体面板。官方基础示例(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>
  • 第 1 个面板:只通过 title 属性传入左侧标题;
  • 第 2 个面板:通过 value 属性传入右侧描述文字;
  • 第 3 个面板:通过具名插槽 #title / #value 自定义标题栏两侧内容。

注意:当 title / value 同时存在属性与插槽两种传入方式时,属性的优先级更高(源码中 $slots.title 存在时优先渲染插槽,否则渲染属性值)。

v-model 双向绑定与 change 事件

默认(非手风琴)模式下,v-model 绑定的是一个数组,数组中的每一项对应一个展开面板的 name。官方示例(demo/data.vue):

<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>

初始状态即展开 name2 与 name3 两个面板。每次切换面板都会触发 change 事件,回调参数为:

参数 说明
modelValue 切换后的完整展开列表(或手风琴模式下的单个 name)
currName 本次被点击切换的面板 name
status 本次操作后的展开状态,true 表示展开,false 表示收起

底层实现:provide/inject 通信

在 collapse.vue 源码中,容器组件维护一个内部状态 innerValue,并通过 COLLAPSE_KEY(定义于 types.ts)向所有子面板注入两个方法:

provide(COLLAPSE_KEY, {
  updateVal,   // 切换面板时由 CollapseItem 调用
  isExpanded   // 判断某个 name 当前是否展开
})

点击某个面板标题时,CollapseItem 执行 parent.updateVal(props.name);容器收到 name 后根据模式更新值并触发 update:modelValue 与 change:

  • 非手风琴模式:若 name 已在数组中则过滤移除(收起),否则追加(展开);
  • 手风琴模式:若当前展开的正是该 name 则置空(收起),否则切换为该 name;
  • 当 v-model 未传入且非手风琴模式时,内部默认初始化为空数组 [];若传入非数组类型,源码会输出 console.warn('[NutUI] <Collapse> 未开启手风琴模式时 v-model 应为数组') 警告。

手风琴模式(Accordion)

通过给 NutCollapse 设置 accordion 属性即可开启手风琴模式:最多只能展开一个面板,此时 v-model 绑定的是单个 name 值(string | number)而非数组。官方示例(demo/accordion.vue):

<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', 'name3'])
const change = (modelValue: string[], name: string, status: boolean) => {
  console.log(modelValue, name, status)
}
</script>

提示:示例中初始值仍为数组,手风琴模式下实际只会生效第一个匹配的面板;更规范的写法是直接 const val = ref('name2')。

在 collapse.spec.tsx 的 should props accordion 测试用例中,同时展开了两个面板的初始值,最终断言展开的标题图标(.nut-collapse-item__title-icon--expanded)数量为 1,验证了手风琴模式下「最多展开一个」的约束;随后点击另一个面板,展开数量仍为 1,证明切换是互斥的。

自定义折叠图标

折叠图标支持两种自定义方式(官方示例 demo/custom.vue):

方式一:slots.icon 插槽(4.2.0 版本新增)

<template>
  <nut-collapse>
    <nut-collapse-item name="name1" title="Title 1">
      <template #icon> <Notice /> </template>
      Content 1
    </nut-collapse-item>
  </nut-collapse>
</template>
<script setup lang="ts">
import { Notice } from '@nutui/icons-vue'
</script>

方式二:props.icon 属性

<template>
  <nut-collapse>
    <nut-collapse-item name="name2" title="Title 2" :icon="Follow"> Content 2 </nut-collapse-item>
  </nut-collapse>
</template>
<script setup lang="ts">
import { Follow } from '@nutui/icons-vue'
</script>

两者均接收 @nutui/icons-vue 图标组件。从 collapse-item.vue 源码看,其渲染优先级为:$slots.icon 插槽存在时渲染插槽内容,否则通过 renderIcon(icon) 动态渲染传入的图标组件;未传入任何图标时,默认使用 DownArrow(向下箭头)图标。

与图标相关的还有 rotate 属性:点击折叠/展开时图标的旋转角度,仅在自定义图标模式下生效,默认值为 180(即展开时默认图标旋转 180°)。容器源码通过行内样式 transform: rotate(...) 控制旋转,同时 index.scss 中为展开状态定义了 .nut-collapse-item__title-icon--expanded 类名,测试用例也通过该类名断言面板展开状态。

禁用状态

给单个 CollapseItem 设置 disabled 属性后,该面板标题栏将不可点击,样式上呈现灰色并取消点击效果。官方示例(demo/disabled.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" disabled> Content 2 </nut-collapse-item>
    <nut-collapse-item name="name3" title="Title 3" disabled> Content 3 </nut-collapse-item>
  </nut-collapse>
</template>

从源码看,禁用通过两个层面实现:

  1. 样式层:标题栏追加 .nut-collapse-item__title--disabled 类,对应 index.scss 中的 pointer-events: none 与 color: $collapse-item-disabled-color,彻底阻断点击并置灰;
  2. 测试层:collapse.spec.tsx 的 should nut-collapse-item props 用例断言了禁用标题的类名包含 --disabled。

设置固定内容(不折叠)

extra 插槽用于在标题下方渲染固定内容——该区域始终展示,不随面板展开/收起。适合放置常驻的操作按钮、提示信息等。官方示例(demo/extra.vue):

<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>

从 collapse-item.vue 源码可以看到,extra 内容渲染在独立的 .nut-collapse__item-extraWrapper 容器中,该容器在 index.scss 中被设置为 height: auto,与参与高度动画的 .nut-collapse__item-wrapper 相互独立,因此永远不会被折叠。

完整 API 一览

Collapse Props

属性 说明 类型 默认值
v-model 当前展开面板的 name 手风琴模式:string | number
非手风琴模式:(string | number)[]
-
accordion 是否开启手风琴模式 boolean false

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

CollapseItem Slots

名称 说明
title 标题栏左侧内容插槽
value 标题栏右侧内容插槽
extra 设置标题下固定内容(不折叠)
icon(4.2.0) 设置自定义图标

Events

事件名 说明 回调参数
change 切换面板时触发 (modelValue, currName, status)

类型定义(v4.3.1)

从 v4.3.1 起,组件导出完整的 TypeScript 类型定义,可直接从包中导入:

import type {
  CollapseValue,
  CollapseProps,
  CollapseInstance,
  CollapseItemProps,
  CollapseItemInstance
} from '@nutui/nutui';

其中 CollapseValue 定义于 types.ts:

export type CollapseValue = string | number | (string | number)[]

这恰好对应手风琴(单个 name)与非手风琴(name 数组)两种模式下的 v-model 值类型。CollapseProps / CollapseItemProps 由各组件源码中的 defineProps 类型直接导出,CollapseInstance / CollapseItemInstance 则是组件实例类型(ComponentPublicInstance & InstanceType<typeof 组件>),见 collapse/index.ts 与 collapseitem/index.ts。

展开动画的源码级实现

Collapse 的展开/收起动画并非简单地切换 v-show,而是通过测量内容高度 + CSS transition 过渡 height 实现的,核心逻辑位于 collapse-item.vue:

  • 容器通过 watch(expanded) 监听面板展开状态;
  • 展开时(open):先将高度置为 0px,在双重 requestAnimationFrame 后测量内容实际高度 contentRef.offsetHeight,再写入具体像素值,触发 height 0.3s ease-in-out 过渡(见 index.scss 的 transition 定义);
  • 收起时(close):先把高度定格为当前内容高度,再在下一帧置为 0px;
  • 过渡结束后通过 transitionend 事件将高度设为 auto,避免固定像素值限制内容自适应。

这也是为什么展开状态的内容区 DOM 类名为 .nut-collapse__item-wrapper__content、而收起/展开切换的测试(见 collapse.spec.tsx 的 should props active 用例)通过断言展开图标类名来验证状态的原因。Taro 版本 collapse-item.taro.vue 实现了相同的逻辑,只是测量改用 Taro 的 API 并生成带随机 id 的内容节点。

主题定制:CSS 变量

Collapse 组件将全部关键样式提取为 CSS 变量,可通过 NutUI 的 ConfigProvider 组件 或直接覆盖变量实现主题定制。完整变量清单如下:

名称 默认值
--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)

这些变量与 collapseitem/index.scss 中引用的 SCSS 变量(如 $collapse-item-padding、$collapse-item-disabled-color、$collapse-wrapper-content-padding 等)一一对应,是组件样式体系的一等公民。除了变量化定制,该样式文件还内置了深色主题适配(.nut-theme-dark)与细线边框(.nut-collapse-item__border 下基于 ::after 伪元素 + scaleY(0.5) 实现的 1px 边框)。

例如将面板间距与内容配色整体调整为品牌风格:

:root {
  --nut-collapse-item-padding: 16px 40px 16px 30px;
  --nut-collapse-item-color: #1a1a1a;
  --nut-collapse-wrapper-content-background-color: #f7f8fa;
}

总结

NutUI 的 Collapse 折叠面板组件以 name 唯一标识 + v-model 状态驱动为核心设计,通过 provide/inject 实现容器与子面板的解耦通信,既支持多面板数组展开,也支持手风琴互斥模式;配合 title / value / icon / extra 插槽与属性、disabled 禁用、rotate 旋转角等细节能力,以及全套 CSS 变量主题定制,可以覆盖绝大多数移动端折叠交互场景。文中涉及的所有示例均来自仓库 demo 目录,接口行为均有 collapse.spec.tsx 测试用例与 collapse.vue、collapse-item.vue 源码支撑,读者可放心在项目中直接复用。

登录后查看全文
nutui