Element Descriptions 描述列表组件实战指南:用法、API 与源码级布局原理
【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element
导读
el-descriptions是 Element(Element UI)中用于"列表形式展示多个字段"的布局组件,适合详情页、用户信息展示、订单核对等只读信息场景。本文以 Element 仓库中的官方文档 examples/docs/zh-CN/descriptions.md 为骨架,完整覆盖基础用法、尺寸控制、垂直布局、自定义样式与全部 API,并结合 packages/descriptions 下的源码实现,为你讲清楚column栅格算法、span合并规则、冒号渲染等底层原理。读完本文,你可以直接照抄示例落地一个详情列表,也能理解组件内部是如何把一组 Item 组装成表格的。
一、组件定位与引入方式
Descriptions 属于 Element 的标准表单展示类组件,在仓库中的代码组织为:
- 组件源码:packages/descriptions/src/index.js(
ElDescriptions主组件) - 条目源码:packages/descriptions/src/descriptions-item.js(
ElDescriptionsItem) - 行渲染源码:packages/descriptions/src/descriptions-row.js(
ElDescriptionsRow) - 注册入口:packages/descriptions/index.js
- 类型声明:types/descriptions.d.ts 与 types/descriptions-item.d.ts
使用上只需引入两个标签:外层el-descriptions负责整体布局(标题、边框、列数、方向、尺寸),内层el-descriptions-item负责单个字段(标签与内容)。它本质上是把若干 Item 渲染成一张table,因此天然支持列对齐与跨列合并。
二、基础用法
最简单的用法是为每个el-descriptions-item设置label作为字段名,把字段值写在标签内:
<el-descriptions title="用户信息"> <el-descriptions-item label="用户名">kooriookami</el-descriptions-item> <el-descriptions-item label="手机号">18100000000</el-descriptions-item> <el-descriptions-item label="居住地">苏州市</el-descriptions-item> <el-descriptions-item label="备注"> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item label="联系地址">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> </el-descriptions>要点说明:
title显示在列表左上方,默认不渲染任何元素;只有传入title、extra或对应插槽时,头部才会出现(见 源码 的render判断)。- 内容区是默认插槽,可以放任意内容,例如示例中的
el-tag,也可以放图片、链接等富内容。 - 未设置
border时默认无边框,采用"标签 + 冒号 + 内容"的紧凑排版。
三、不同尺寸:size 与全局默认值
Descriptions 支持medium / small / mini三种尺寸(默认继承全局配置),可以用一个单选组动态切换:
<template> <el-radio-group v-model="size"> <el-radio label="">默认</el-radio> <el-radio label="medium">中等</el-radio> <el-radio label="small">小型</el-radio> <el-radio label="mini">超小</el-radio> </el-radio-group> <el-descriptions class="margin-top" title="带边框列表" :column="3" :size="size" border> <template slot="extra"> <el-button type="primary" size="small">操作</el-button> </template> <el-descriptions-item> <template slot="label"> <i class="el-icon-user"></i> 用户名 </template> kooriookami </el-descriptions-item> <el-descriptions-item> <template slot="label"> <i class="el-icon-mobile-phone"></i> 手机号 </template> 18100000000 </el-descriptions-item> <el-descriptions-item> <template slot="label"> <i class="el-icon-location-outline"></i> 居住地 </template> 苏州市 </el-descriptions-item> <el-descriptions-item> <template slot="label"> <i class="el-icon-tickets"></i> 备注 </template> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item> <template slot="label"> <i class="el-icon-office-building"></i> 联系地址 </template> 江苏省苏州市吴中区吴中大道 1188 号 </el-descriptions-item> </el-descriptions> <el-descriptions class="margin-top" title="无边框列表" :column="3" :size="size"> <template slot="extra"> <el-button type="primary" size="small">操作</el-button> </template> <el-descriptions-item label="用户名">kooriookami</el-descriptions-item> <el-descriptions-item label="手机号">18100000000</el-descriptions-item> <el-descriptions-item label="居住地">苏州市</el-descriptions-item> <el-descriptions-item label="备注"> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item label="联系地址">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> </el-descriptions> </template> <script> export default { data () { return { size: '' }; } } </script>值得注意的实现细节(见 源码):
descriptionsSize() { return this.size || (this.$ELEMENT || {}).size; }即:当未显式传入size时,组件会回退读取全局配置$ELEMENT.size。这与 Element 的 Button、Input 等组件的尺寸约定一致,便于整个项目统一控制密度。尺寸最终以el-descriptions--medium/small/mini类名作用到表格上,对应的内边距与字号定义在 packages/theme-chalk/src/descriptions.scss:
| 尺寸 | 带边框内边距 | 无边框下边距 | 字号 |
|---|---|---|---|
| 默认 | 12px 10px | 12px | 基础字号 |
| medium | 10px | 10px | 基础字号 |
| small | 8px 10px | 8px | 12px |
| mini | 6px 10px | 6px | 12px |
本示例同时演示了
slot="extra"插槽:它显示在右上角,可放置按钮等操作区;此外示例中用template slot="label"配合el-icon-*图标实现了带图标的标签,这说明label插槽优先级高于label属性。
四、垂直列表:direction="vertical"
默认direction="horizontal"是"标签在左、内容在右"的横排;设置为vertical后,标签与内容各自独占一行,形成上下结构的列表:
<el-descriptions title="垂直带边框列表" direction="vertical" :column="4" border> <el-descriptions-item label="用户名">kooriookami</el-descriptions-item> <el-descriptions-item label="手机号">18100000000</el-descriptions-item> <el-descriptions-item label="居住地" :span="2">苏州市</el-descriptions-item> <el-descriptions-item label="备注"> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item label="联系地址">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> </el-descriptions> <el-descriptions class="margin-top" title="垂直无边框列表" :column="4" direction="vertical"> <el-descriptions-item label="用户名">kooriookami</el-descriptions-item> <el-descriptions-item label="手机号">18100000000</el-descriptions-item> <el-descriptions-item label="居住地" :span="2">苏州市</el-descriptions-item> <el-descriptions-item label="备注"> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item label="联系地址">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> </el-descriptions>上例中:span="2"让"居住地"在column="4"下横向占用两列宽度。从行渲染源码(descriptions-row.js)可以看到,垂直模式下每个 Item 被渲染为两行:
- 第一行
<tr>内放若干<th class="el-descriptions-item__label">,colSpan等于该 Item 的span; - 第二行
<tr>内放对应的<td class="el-descriptions-item__content">,同样按span设置colSpan。
这也是为什么垂直列表在视觉上是"标签一行、内容一行"的原因。官方单元测试 test/unit/specs/descriptions.spec.js 对两种方向做了断言:水平模式下每行子节点数是column × 2(标签 + 内容),切换为垂直后每行子节点数等于column,且上下两行对应单元格内容一致。
五、自定义样式:类名、内联样式与样式覆盖
Descriptions 允许在组件级与条目级分别自定义标签和内容的类名与内联样式:
<el-descriptions title="自定义样式列表" :column="3" border> <el-descriptions-item label="用户名" label-class-name="my-label" content-class-name="my-content">kooriookami</el-descriptions-item> <el-descriptions-item label="手机号">18100000000</el-descriptions-item> <el-descriptions-item label="居住地">苏州市</el-descriptions-item> <el-descriptions-item label="备注"> <el-tag size="small">学校</el-tag> </el-descriptions-item> <el-descriptions-item label="联系地址" :contentStyle="{'text-align': 'right'}">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item> </el-descriptions> <style> .my-label { background: #E1F3D8; } .my-content { background: #FDE2E2; } </style>使用建议与实现要点:
label-class-name/content-class-name会被拼接到th/td的class上,配合普通 CSS 即可定制背景色、字体等;:contentStyle传入对象作为内联样式(示例中让"联系地址"内容右对齐);- 在 DOM 模板(非构建环境)中应使用 kebab-case 形式
label-class-name、content-style等;在 SFC 模板中两种写法均有效; - 条目级样式会覆盖组件级同名配置。源码中 descriptions-row.js 对
labelClassName / contentClassName / labelStyle / contentStyle四个属性做了合并:res[key] = item.props[key] || elDescriptions[key],即 Item 有值用 Item 的,否则回退到el-descriptions上的值; - 标签单元格还内置了
.is-bordered-label样式:带边框时标签背景为浅灰、字体加粗(见 descriptions-item.scss)。
另外,模板示例中直接使用:contentStyle是组件内部 props 的 camelCase 写法,与types/descriptions.d.ts中声明的labelStyle: object、contentStyle: object保持一致。
六、完整 API 一览
Descriptions Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| border | 是否带有边框 | boolean | — | false |
| column | 一行Descriptions Item的数量 | number | — | 3 |
| direction | 排列的方向 | string | vertical / horizontal | horizontal |
| size | 列表的尺寸 | string | medium / small / mini | — |
| title | 标题文本,显示在左上方 | string | — | — |
| extra | 操作区文本,显示在右上方 | string | — | — |
| colon | 是否显示冒号 | boolean | — | true |
| labelClassName | 自定义标签类名 | string | — | — |
| contentClassName | 自定义内容类名 | string | — | — |
| labelStyle | 自定义标签样式 | object | — | — |
| contentStyle | 自定义内容样式 | object | — | — |
Descriptions Slots
| Name | 说明 |
|---|---|
| title | 自定义标题,显示在左上方 |
| extra | 自定义操作区,显示在右上方 |
Descriptions Item Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| label | 标签文本 | string | — | — |
| span | 列的数量 | number | — | 1 |
| labelClassName | 自定义标签类名 | string | — | — |
| contentClassName | 自定义内容类名 | string | — | — |
| labelStyle | 自定义标签样式 | object | — | — |
| contentStyle | 自定义内容样式 | object | — | — |
Descriptions Item Slots
| Name | 说明 |
|---|---|
| label | 自定义标签文本 |
七、源码原理:column 栅格算法与三种渲染分支
了解 API 之后,再看组件内部如何工作。ElDescriptionsItem本身是不渲染任何 DOM 的"配置节点"——它的render()直接返回null(见 descriptions-item.js),只承载label / span等 props。真正把数据变成表格的是主组件的getRows()与行组件ElDescriptionsRow。
1.getRows()的栅格算法
见 index.js。核心逻辑:
- 从默认插槽中筛选出
name === 'ElDescriptionsItem'的 vnode 作为数据源; - 通过
getOptionProps读取每个 Item 的 props(含默认值),通过getSlots收集label与默认插槽内容; - 用计数器
count = this.column模拟"每行还能放多少列":span < count:当前 Item 放进本行,count -= span;span >= count:当前 Item 用filledNode补齐(若span > count会钳制为 count),本行封口,count重置为column;- 最后一项无论 span 多少,都会被强制设置为整行剩余宽度(
filledNode(..., isLast = true)中node.props.span = count),保证最后一行表格完整、不会出现空单元格。
2. 三种渲染分支
ElDescriptionsRow根据direction与border组合出三种结构(descriptions-row.js):
| 场景 | DOM 结构 | 说明 |
|---|---|---|
direction="vertical" | 标签<tr><th>+ 内容<tr><td> | 两行结构,colSpan = span |
border水平 | 单行内<th>+<td>成对出现 | td的colSpan = span * 2 - 1,实现"标签列 + 内容列合并"的对齐 |
| 无边框水平 | 单行<td>,内部用 flex 容器包裹标签与内容 | 每个 Item 只占一个单元格 |
3. 冒号(colon)是如何渲染的
colon属性默认为true。在无边框水平模式下,标签span会带上.has-colon类,由 CSS 伪元素生成冒号(descriptions-item.scss):
.el-descriptions-item__label { &.has-colon { &::after { content: ':'; position: relative; top: -0.5px; } } }注意:垂直模式与带边框模式下,has-colon会被显式移除(见 descriptions-row.js 与 L98-L99),避免与边框样式中独立的标签单元格视觉冲突。
八、响应式更新与测试佐证
组件对插槽内容的变化是响应式的。单元测试 descriptions.spec.js 中有一个典型场景:通过v-for渲染多个el-descriptions,点击按钮用this.$set修改备注内容后,断言el-tag的文本同步更新为'company'。这说明默认插槽内容会随数据变化自动重渲染,可用于动态详情页。
其余测试还覆盖了:
- 标题与操作区文本渲染(L10-L25);
border是否生成is-bordered类(L27-L40);- 组件级
label-class-name/content-class-name的传递(L42-L55); column变化时每行子节点数的联动(L57-L75);span是否正确写入colSpan属性(L113-L125)。
九、实战小结
- 详情展示优先使用默认水平无边框模式,信息密度高且视觉干净;
- 需要强分隔时加
border,此时标签列自动获得浅灰底色与加粗样式; - 字段较多、需要换行对齐时,配合
column与span规划栅格,最后一行会自动补全宽度; - 需要图标标签或富内容时,使用
label插槽与默认插槽; - 全项目统一密度时,不必逐个写
size,直接配置$ELEMENT.size即可让 Descriptions 自动继承。
相关文件速查:官方文档 examples/docs/zh-CN/descriptions.md · 组件实现 packages/descriptions/src/index.js · 行渲染 packages/descriptions/src/descriptions-row.js · 样式 packages/theme-chalk/src/descriptions.scss · 类型声明 types/descriptions.d.ts。
【免费下载链接】elementA Vue.js 2.0 UI Toolkit for Web项目地址: https://gitcode.com/gh_mirrors/eleme/element
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考