I. Component introduction
Website links: Descriptions component | Element (gitee. IO).
El-descriptions Component is a presentation component that uses tables to display information of multiple fields. Need to be used with el-Descriptions – Item sub-components.
1.1 descriptions properties
- Border: Boolean type, whether to display a border;
- Direction: string direction of label and content. That is, label is on the left or above. Optional values:
horizontal/vertical
, default value:horizontal
; - Size: Indicates the size of the list. This value is optional
medium/small/mini
, default value:medium
; - Column: number type, showing how many units in a row
descriptions-item
Default value: 3. - Title: String, title, shown in the upper left;
- Extra: String type, operation area text, displayed in the upper right;
1.2 descriptions – item properties
- Label: string type, label text.
- Span: number type, the number of units of width occupied by the column, and
descriptions
Column is related to; - Width: number/string;
- Min-width: number/string
min-width
The remaining width will be proportionally allocated to the Settingsmin-width
The column; - Align /label-align: string, alignment of column content /label. Optional values:
left / center / right
, default value:left
; - Class-name /label-class-name: : user-defined class name of column content /label.
Second, source code analysis
2.1 descriptions source
2.1.1 template section
<template>
<div class="el-descriptions">
<! -- Head, left to show the title, right to show the operation area -->
<div v-if="title || extra || $slots.title || $slots.extra" class="el-descriptions__header">
<! - the title -- -- >
<div class="el-descriptions__title">
<slot name="title">{{ title }}</slot>
</div>
<! -- Operation area -->
<div class="el-descriptions__extra">
<slot name="extra">{{ extra }}</slot>
</div>
</div>
<div class="el-descriptions__body">
<! Use table to display -->
<table
:class="['el-descriptions__table', { 'is-bordered': border }, descriptionsSize ? `el-descriptions--${descriptionsSize}` : '']"
>
<! -- No thead -->
<tbody>
<! -- Loop rows -->
<template v-for="(row, index) in getRows()" :key="index">
<el-descriptions-row :row="row" />
</template>
</tbody>
</table>
</div>
</div>
</template>
Copy the code
2.1.2 script part
setup(props, { slots }) {
// Provide data to child components
provide(elDescriptionsKey, props)
const $ELEMENT = useGlobalConfig()
// Size of the component
const descriptionsSize = computed(() = > {
return props.size || $ELEMENT.size
})
// Data flattening method: use recursion to make nested data into a one-dimensional array
const flattedChildren = children= > {
const temp = Array.isArray(children) ? children : [children]
const res = []
temp.forEach(child= > {
if (Array.isArray(child.children)) { res.push(... flattedChildren(child.children)) }else {
res.push(child)
}
})
return res
}
// Set the width of the column based on the remaining width units of the current row
const filledNode = (node, span, count, isLast = false) = > {
if(! node.props) {// Avoid undefined for node.props to set span later
node.props = {}
}
if (span > count) {
// If the span setting for the current column exceeds the remaining width units, set to the remaining width units
node.props.span = count
}
if (isLast) {
// If it is an element in the last column, set it to its own width unit
node.props.span = span
}
return node
}
const getRows = () = > {
// Flatters the ElDescriptionsItem element in the default slot to get the one-dimensional array
// Each element in a one-dimensional array is a column
const children = flattedChildren(slots.default?.()).filter(node= >node? .type? .name ==='ElDescriptionsItem')
/ / rows of data
const rows = []
// A temporary array to store columns for each row
let temp = []
// count is the number of units of width left to allocate for the current row
let count = props.column
// Total the width units occupied by each column
let totalSpan = 0
// Loop through each column
children.forEach((node, index) = > {
// The unit of width occupied by the current column
letspan = node.props? .span ||1
// is not the last column element
if (index < children.length - 1) {
// Accumulate the width units occupied by each column
totalSpan += (span > count ? count : span)
}
// Last column element
if (index === children.length - 1) {
// Calculate the remaining width units that the last element can occupy
const lastSpan = props.column - totalSpan % props.column
// Push the last column element into a temporary row
temp.push(filledNode(node, lastSpan, count, true))
// The last column element is placed in a temporary row. Push the temporary row into rows
rows.push(temp)
return
}
// Not the last column element
// If the column width is smaller than the remaining width of the current row, it is placed in the current temporary row
if (span < count) {
// The unit of the remaining width of the current row minus the current column element
count -= span
// The current column element is put into the current temporary row
temp.push(node)
} else {
// If the column element width is greater than or equal to the remaining width of the current row
// Place the column element into the current row, using filledNode to set the width to the remaining width of the current row
temp.push(filledNode(node, span, count))
// The current column element is put into the current temporary row
rows.push(temp)
// Resets the state to start a new line
/ / restore the count
count = props.column
// Clear the current temporary row
temp = []
}
})
return rows
}
return {
descriptionsSize,
getRows,
}
}
Copy the code
2.1.3 summary
- The header part uses slots provided
title/extra
2 slots; - Use table to display data, not thead, use TBody;
- According to the columns attribute and the default slot
descriotions-item
The span attribute of the element, generating each row row by row;
2.2 descriptions – row source code
2.2.1 script part
<template>
<! -- Vertical layout -->
<template v-if="descriptions.direction === 'vertical'">
<! -- Render 2 tr for each row, label and content
<tr>
<! - lable line -- -- >
<template v-for="(cell, index) in row" :key="`tr1-${index}`">
<el-descriptions-cell :cell="cell" tag="th" type="label" />
</template>
</tr>
<tr>
<! -- Content line -->
<template v-for="(cell, index) in row" :key="`tr2-${index}`">
<el-descriptions-cell :cell="cell" tag="td" type="content" />
</template>
</tr>
</template>
<! Horizontal layout -->
<! -- Render 1 tr per row -->
<tr v-else>
<template v-for="(cell, index) in row" :key="`tr3-${index}`">
<! -- With borders -->
<template v-if="descriptions.border">
<el-descriptions-cell :cell="cell" tag="td" type="label" />
<el-descriptions-cell :cell="cell" tag="td" type="content" />
</template>
<! -- No border -->
<el-descriptions-cell
v-else
:cell="cell"
tag="td"
type="both"
/>
</template>
</tr>
</template>
Copy the code
2.2.2 script part
setup() {
// Inject data provided by the Descriptions component
const descriptions = inject(elDescriptionsKey, {} as IDescriptionsInject)
return {
descriptions,
}
},
Copy the code
2.3 the description – the cell source
export default defineComponent({
name: "ElDescriptionsCell".props: {
cell: {
type: Object,},tag: {
type: String,},type: {
type: String,}},setup() {
// Inject data provided by the Descriptions component
const descriptions = inject(elDescriptionsKey, {} as IDescriptionsInject);
return {
descriptions,
};
},
render() {
// props property formatting
const item = getNormalizedProps(this.cell as VNode) as IDescriptionsItemInject;
// Descriptions - Item Label slot takes precedence over label attribute
const label = this.cell? .children? .label? .() || item.label;// Content is the default slot of Descriptions -item
const content = this.cell? .children? .default?.();const span = item.span;
const align = item.align ? `is-${item.align}` : "";
const labelAlign = item.labelAlign ? `is-${item.labelAlign}` : "" || align;
const className = item.className;
const labelClassName = item.labelClassName;
const style = {
width: addUnit(item.width),
minWidth: addUnit(item.minWidth),
};
// Render different content according to type
switch (this.type) {
case "label":
/ / to render the label
return h(
this.tag,
{
style: style,
class: [
"el-descriptions__cell"."el-descriptions__label",
{ "is-bordered-label": this.descriptions.border },
labelAlign,
labelClassName,
],
colSpan: this.descriptions.direction === "vertical" ? span : 1,
},
label
);
case "content":
/ / render the content
return h(
this.tag,
{
style: style,
class: ["el-descriptions__cell"."el-descriptions__content", align, className],
colSpan: this.descriptions.direction === "vertical" ? span : span * 2 - 1,
},
content
);
default:
// type is both; A TD contains both label and content
return h(
"td",
{
style: style,
class: [align],
colSpan: span,
},
[
h(
"span",
{
class: ["el-descriptions__cell"."el-descriptions__label", labelClassName],
},
label
),
h(
"span",
{
class: ["el-descriptions__cell"."el-descriptions__content", className], }, content ), ] ); }}});Copy the code