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 optionalmedium/small/mini, default value:medium;
  • Column: number type, showing how many units in a rowdescriptions-itemDefault 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, anddescriptionsColumn is related to;
  • Width: number/string;
  • Min-width: number/stringmin-widthThe remaining width will be proportionally allocated to the Settingsmin-widthThe 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

  1. The header part uses slots providedtitle/extra2 slots;
  2. Use table to display data, not thead, use TBody;
  3. According to the columns attribute and the default slotdescriotions-itemThe 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