본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

Table 표 — 행렬 데이터를 보기 좋게 꾸며 주는 컴포넌트

원문 보기 위키 갱신

vue-devui의 d-table은 구조화된 데이터를 행과 열로 깔끔하게 보여 주는 테이블 컴포넌트예요. 정렬, 필터, 체크박스 선택 같은 실무에서 꼭 필요한 기능도 곁들여 제공해요.

출처: 문서

본문

행과 열로 이루어진 데이터를 표시해요.

언제 사용하나요?

  1. 대량의 구조화된 데이터를 보여 줄 때;
  2. 데이터를 정렬·필터링하거나 사용자 지정 작업 등 복잡한 동작이 필요할 때.

기본 사용법

:::demo 간단한 표예요. d-table 컴포넌트의 data 속성에 표시할 데이터를 넘기고, d-column 컴포넌트에서는 field로 해당 열의 필드 이름을, header로 열 제목을 지정해요.

<template>
  <d-table :data="baseTableData" style="width: 100%">
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const baseTableData = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    return { baseTableData };
  },
});
</script>

:::

표 스타일

:::demo table-layout 파라미터는 표의 레이아웃 방식을 정해요. 현재 fixed와 auto 두 가지 타입을 지원해요. striped는 줄무늬(zebra) 행을 표시할지 정하고, header-bg는 표헤더 배경색을 표시할지 정해요. size는 표 크기, border-type은 표 테두리 스타일을 설정해요.

<template>
  <div class="table-btn-groups mb-2">
    <div class="table-btn mb-1">
      自动表格布局:
      <d-switch v-model="tableLayout" />
    </div>
    <div class="table-btn mb-1">
      斑马纹:
      <d-switch v-model="striped" />
    </div>
    <div class="table-btn mb-1">
      表头背景色:
      <d-switch v-model="headerBg" />
    </div>
    <div class="table-btn mb-1">
      表格大小:
      <d-radio-group direction="row" v-model="size">
        <d-radio v-for="item in sizeList" :key="item.label" :value="item.value">
          {{ item.label }}
        </d-radio>
      </d-radio-group>
    </div>
    <div class="table-btn mb-1">
      表格边框:
      <d-radio-group direction="row" v-model="borderType">
        <d-radio v-for="item in borderTypeList" :key="item.label" :value="item.value">
          {{ item.label }}
        </d-radio>
      </d-radio-group>
    </div>
    <div class="table-btn mb-1">
      表头显隐:
      <d-switch v-model="showHeader" />
    </div>
  </div>
  <d-table
    :table-layout="tableLayout ? 'auto' : 'fixed'"
    :striped="striped"
    :header-bg="headerBg"
    :data="stripedTableData"
    :size="size"
    :border-type="borderType"
    :show-header="showHeader"
  >
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const tableLayout = ref(false);
    const striped = ref(false);
    const headerBg = ref(false);
    const size = ref('sm');
    const borderType = ref('');
    const showHeader = ref(true);
    const sizeList = [
      {
        label: 'Normal',
        value: 'sm',
      },
      {
        label: 'Middle',
        value: 'md',
      },
      {
        label: 'large',
        value: 'lg',
      },
    ];
    const borderTypeList = [
      {
        label: 'Normal',
        value: '',
      },
      {
        label: 'Bordered',
        value: 'bordered',
      },
      {
        label: 'Borderless',
        value: 'borderless',
      },
    ];
    const stripedTableData = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    return {
      stripedTableData,
      striped,
      headerBg,
      size,
      sizeList,
      borderType,
      showHeader,
      borderTypeList,
      tableLayout,
    };
  },
});
</script>

<style lang="scss">
.table-btn-groups {
  display: flex;
  flex-wrap: wrap;
}
.table-btn {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-right: 1rem;
}
</style>

:::

표 인터랙션

:::demo d-column을 하나 추가하고 type 속성을 checkable로 설정하면 표에서 여러 행을 선택하는 기능을 만들 수 있어요. getCheckedRows 메서드는 선택된 목록을 가져오고, toggleRowSelection 메서드는 특정 행의 선택 상태를 토글해요. cell-click 이벤트로 셀 클릭을 감지할 수 있는데, 이벤트 콜백 인자에는 행 인덱스, 열 인덱스, 행 데이터, 열 데이터가 담겨 있어요. 열에 resizeable 속성을 설정하면 해당 열의 폭을 드래그로 바꿀 수 있고, min-width와 max-width로 드래그 가능 범위를 정해요. resize-start, resizing, resize-end 이벤트는 각각 드래그 시작 시, 진행 중, 끝난 뒤에 발생해요.

<template>
  <div>
    <div class="mb-2">
      <d-button @click="handleClick" class="mr-1">Get CheckedRows</d-button>
      <d-button @click="insertRow" class="mr-1">Insert Row</d-button>
      <d-button @click="deleteRow" class="mr-1">Delete Row</d-button>
      <d-button @click="toggleRow" class="mr-1">Toggle Row</d-button>
    </div>
    <d-table
      ref="tableRef"
      :data="data"
      :row-key="(item) => item.id"
      @cell-click="onCellClick"
      @row-click="onRowClick"
      @check-change="checkChange"
      @check-all-change="checkAllChange"
    >
      <d-column type="checkable" width="40" :checkable="checkable" reserve-check></d-column>
      <d-column field="firstName" header="First Name" width="200"></d-column>
      <d-column
        field="lastName"
        header="Last Name"
        width="200"
        resizeable
        min-width="150"
        max-width="250"
        @resize-start="onResizeStart"
        @resizing="onResizing"
        @resize-end="onResizeEnd"
      ></d-column>
      <d-column field="gender" header="Gender"></d-column>
      <d-column field="date" header="Date of birth"></d-column>
    </d-table>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const tableRef = ref();
    const data = ref([
      {
        id: '0',
        firstName: 'po',
        lastName: 'Lang',
        gender: 'Male',
        date: '1990/01/15',
      },
      {
        id: '1',
        firstName: 'john',
        lastName: 'Li',
        gender: 'Female',
        date: '1990/01/16',
      },
      {
        id: '2',
        firstName: 'peng',
        lastName: 'Li',
        gender: 'Male',
        date: '1990/01/17',
      },
      {
        id: '3',
        firstName: 'Dale',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/18',
      },
    ]);
    const handleClick = () => {
      console.log(tableRef.value.store.getCheckedRows());
    };
    const onCellClick = (params) => {
      console.log('cell-click', params);
    };
    const onRowClick = (params) => {
      console.log('row-click', params);
    };

    const checkChange = (checked, row, selection) => {
      console.log('checked row:', checked, row, selection);
    };

    const checkAllChange = (checked, selection) => {
      console.log('checked:', checked, selection);
    };

    const toggleRow = () => {
      tableRef.value.store.toggleRowSelection(data.value[0]);
    };

    const checkable = (row, rowIndex) => {
      return row.lastName === 'Li' || false;
    };

    const insertRow = () => {
      data.value.push({
        id: `${data.value.length}`,
        firstName: 'Jeff',
        lastName: 'You',
        gender: 'Male',
        date: '1989/05/19',
      });
    };

    const deleteRow = () => {
      data.value.splice(0, 1);
    };

    const onResizeStart = (e) => {
      console.log('resize start', e);
    };

    const onResizing = (e) => {
      console.log('resizing', e);
    };

    const onResizeEnd = (e) => {
      console.log('resize end', e);
    };

    return {
      tableRef,
      data,
      handleClick,
      onCellClick,
      onRowClick,
      checkChange,
      checkAllChange,
      checkable,
      insertRow,
      deleteRow,
      onResizeStart,
      onResizing,
      onResizeEnd,
      toggleRow,
    };
  },
});
</script>

:::

인덱스 열

:::demo d-column을 하나 추가하고 type 파라미터를 index로 설정하면 표에 인덱스를 붙일 수 있어요. 인덱스 열의 표헤더는 기본적으로 #을 표시하고, header 파라미터로 원하는 내용을 지정할 수도 있어요.

<template>
  <div>
    <d-table :data="data">
      <d-column type="index" width="40"></d-column>
      <d-column field="firstName" header="First Name"></d-column>
      <d-column field="lastName" header="Last Name"></d-column>
      <d-column field="gender" header="Gender"></d-column>
      <d-column field="date" header="Date of birth"></d-column>
    </d-table>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const data = ref([
      {
        firstName: 'po',
        lastName: 'Lang',
        gender: 'Male',
        date: '1990/01/15',
      },
      {
        firstName: 'john',
        lastName: 'Li',
        gender: 'Female',
        date: '1990/01/16',
      },
      {
        firstName: 'peng',
        lastName: 'Li',
        gender: 'Male',
        date: '1990/01/17',
      },
      {
        firstName: 'Dale',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/18',
      },
    ]);

    return { data };
  },
});
</script>

:::

사용자 지정 열

:::demo d-column 하위 컴포넌트가 제공하는 default 슬롯으로 열 내용을 자유롭게 꾸밀 수 있어요. 슬롯은 행 데이터와 행 인덱스를 뜻하는 row와 rowIndex 두 파라미터를 넘겨 줘요.

<template>
  <d-table :data="dataSource">
    <d-column type="index" width="80">
      <template #default="scope">
        {{ `No.${scope.rowIndex + 1}` }}
      </template>
    </d-column>
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
    <d-column header="Operation" align="right">
      <template #default="scope">
        <d-button @click="handleClick(scope.row)">编辑</d-button>
      </template>
    </d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);
    const handleClick = (row) => {
      console.log(row);
    };

    return { dataSource, handleClick };
  },
});
</script>

:::

셀 편집

:::demo d-column 하위 컴포넌트가 제공하는 cell, cellEdit 슬롯과 type 속성을 editable로 설정하면 셀을 편집할 수 있어요. 이 기능은 row-key 속성과 함께 사용해야 해요.

<template>
  <d-table ref="tableRef" :data="dataSource" row-key="id" @cellClick="cellClick">
    <d-column type="index" width="80">
      <template #default="scope">
        {{ `No.${scope.rowIndex + 1}` }}
      </template>
    </d-column>
    <d-column field="firstName" header="First Name" type="editable">
      <template #cell="scope">
        {{ scope.row.firstName }}
      </template>
      <template #cellEdit="scope">
        <d-input
          ref="firstNameRef"
          v-model="scope.row.firstName"
          @change="(value) => change(scope.row, scope.rowIndex, 'firstName', value)"
          @blur="() => blur(scope.row, scope.rowIndex, 'firstName')"
        />
      </template>
    </d-column>
    <d-column field="lastName" header="Last Name" type="editable">
      <template #cell="scope">
        {{ scope.row.lastName }}
      </template>
      <template #cellEdit="scope">
        <d-input
          ref="lastNameRef"
          v-model="scope.row.lastName"
          @change="(value) => change(scope.row, scope.rowIndex, 'lastName', value)"
          @blur="() => blur(scope.row, scope.rowIndex, 'lastName')"
        />
      </template>
    </d-column>
    <d-column field="gender" header="Gender" type="editable">
      <template #cell="scope">
        {{ scope.row.gender }}
      </template>
      <template #cellEdit="scope">
        <d-select
          ref="genderRef"
          v-model="scope.row.gender"
          :options="options"
          @valueChange="(value) => change(scope.row, scope.rowIndex, 'gender', value)"
        />
      </template>
    </d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref, nextTick } from 'vue';

export default defineComponent({
  setup() {
    const tableRef = ref();
    const firstNameRef = ref();
    const lastNameRef = ref();
    const genderRef = ref();
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        gender: 'Male',
        id: 'Mark',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        id: 'Jacob',
      },
    ]);

    const options = ref(['Female', 'Male']);

    const change = (row, rowIndex, field, value) => {
      dataSource.value[rowIndex][field] = typeof value === 'object' ? value.value : value;
      tableRef.value.store.setCellMode(row, rowIndex, field, 'readonly');
    };
    const blur = (row, rowIndex, field) => {
      tableRef.value.store.setCellMode(row, rowIndex, field, 'readonly');
    };

    const cellClick = (obj) => {
      tableRef.value.store.setCellMode(obj.row, obj.rowIndex, obj.column.field, 'edit');
      const refMap = {
        firstName: firstNameRef,
        lastName: lastNameRef,
        gender: genderRef,
      };
      const targetRef = refMap[obj.column.field];
      nextTick(() => {
        targetRef?.value?.focus();
      });
    };

    return { tableRef, firstNameRef, lastNameRef, genderRef, dataSource, options, change, blur, cellClick };
  },
});
</script>

:::

사용자 지정 표헤더

:::demo d-column 하위 컴포넌트가 제공하는 header 슬롯으로 표헤더를 자유롭게 꾸밀 수 있어요.

<template>
  <d-table :data="dataSource">
    <d-column field="firstName">
      <template #header>
        <div>
          <span style="margin-right:4px;font-size:var(--devui-font-size,12px)">First Name</span>
          <d-popover content="some tips text" trigger="hover" :position="['top']">
            <d-icon name="info-o"></d-icon>
          </d-popover>
        </div>
      </template>
    </d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    return { dataSource };
  },
});
</script>

:::

빈 데이터 템플릿

:::demo 넘겨준 데이터가 비어 있으면 기본 빈 데이터 템플릿을 보여 줘요.

<template>
  <div>
    <d-button @click="handleClick">更新数据</d-button>
    <d-table :data="emptyData" :show-loading="showLoading">
      <d-column field="firstName" header="First Name"></d-column>
      <d-column field="lastName" header="Last Name"></d-column>
      <d-column field="gender" header="Gender"></d-column>
      <d-column field="date" header="Date of birth"></d-column>
      <template #empty>
        <div style="text-align: center;">No Data</div>
      </template>
    </d-table>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const emptyData = ref([]);
    const showLoading = ref(false);
    const handleClick = () => {
      showLoading.value = true;
      setTimeout(() => {
        showLoading.value = false;
        emptyData.value = [
          {
            firstName: 'po',
            lastName: 'Lang',
            gender: 'Male',
            date: '1990/01/15',
          },
          {
            firstName: 'john',
            lastName: 'Li',
            gender: 'Female',
            date: '1990/01/16',
          },
          {
            firstName: 'peng',
            lastName: 'Li',
            gender: 'Male',
            date: '1990/01/17',
          },
          {
            firstName: 'Dale',
            lastName: 'Yu',
            gender: 'Female',
            date: '1990/01/18',
          },
        ];
      }, 1000);
    };

    return { emptyData, showLoading, handleClick };
  },
});
</script>

:::

고정 표헤더

:::demo fix-header 파라미터로 표헤더를 고정할 수 있어요. 내용이 스크롤되어도 표헤더는 그 자리에 남아요.

<template>
  <d-table :data="dataSource" table-height="200px" fix-header style="width:100%">
    <d-column field="firstName" header="First Name" :width="150"></d-column>
    <d-column field="lastName" header="Last Name" :width="150"></d-column>
    <d-column field="gender" header="Gender" :width="150"></d-column>
    <d-column field="date" header="Date of birth" :width="150"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'po',
        lastName: 'lang',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'john',
        lastName: 'li',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'peng',
        lastName: 'li',
        gender: 'Female',
        date: '1990/01/14',
      },
      {
        firstName: 'Danni',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/14',
      },
    ]);

    return { dataSource };
  },
});
</script>

:::

고정 열

:::demo 표의 열이 많을 때는 고정 열이 좋아요. 좌우로 스크롤하면서도 데이터를 쉽게 찾고 비교할 수 있게 도와주거든요. fixed-left와 fixed-right로 설정해요.

<template>
  <d-table :data="tableDataFixedColumn" table-layout="auto">
    <d-column field="idNo" header="ID Card Number" fixed-left="0px"></d-column>
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
    <d-column field="address" header="Address"></d-column>
    <d-column field="occupation" header="Occupation"></d-column>
    <d-column field="occupation" header="Occupation"></d-column>
    <d-column field="occupation" header="Occupation"></d-column>
    <d-column field="occupation" header="Occupation" fixed-right="0px"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref, computed } from 'vue';

export default defineComponent({
  setup() {
    const tableDataFixedColumn = ref([
      {
        firstName: 'po',
        lastName: 'Lang',
        gender: 'Male',
        date: '1990/01/15',
        address: 'Shenzhen Guangdong',
        occupation: 'Worker',
        idNo: '2000**********9999',
      },
      {
        firstName: 'john',
        lastName: 'Li',
        gender: 'Female',
        date: '1990/01/16',
        address: 'Shenzhen Guangdong',
        occupation: 'Worker',
        idNo: '2000**********9999',
      },
      {
        firstName: 'peng',
        lastName: 'Li',
        gender: 'Male',
        date: '1990/01/17',
        address: 'Shenzhen Guangdong',
        occupation: 'Worker',
        idNo: '2000**********9999',
      },
      {
        firstName: 'Dale',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/18',
        address: 'Shenzhen Guangdong',
        occupation: 'Worker',
        idNo: '2000**********9999',
      },
    ]);

    const filterList = computed(() =>
      tableDataFixedColumn.value.map((item) => ({ name: `${item.firstName} ${item.lastName}`, value: item.firstName }))
    );

    return {
      tableDataFixedColumn,
      filterList,
    };
  },
});
</script>

:::

셀 병합

:::demo span-method 메서드로 셀 병합을 직접 정의할 수 있어요. 메서드 인자는 객체인데, 현재 행 row, 현재 열 column, 현재 행 인덱스 rowIndex, 현재 열 인덱스 columnIndex 속성을 담고 있어요. 이 메서드는 rowspan과 colspan 두 요소를 담은 배열을 반환하거나, rowspan과 colspan 속성을 가진 객체를 반환할 수 있어요.

<template>
  <d-table :data="dataSource" :span-method="spanMethod" border-type="bordered">
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);
    const spanMethod = ({ row, column, rowIndex, columnIndex }) => {
      if (rowIndex === 0 && columnIndex === 0) {
        return { rowspan: 1, colspan: 2 };
      }
      if (rowIndex === 2 && columnIndex === 0) {
        return [2, 2];
      }
      if (rowIndex === 2 && columnIndex === 3) {
        return [2, 1];
      }
    };

    return { dataSource, spanMethod };
  },
});
</script>

:::

표헤더 그룹

:::demo d-column 안에 d-column을 중첩하면 표헤더를 그룹으로 묶을 수 있어요.

<template>
  <d-table :data="dataSource">
    <d-column field="name" header="Name">
      <d-column field="firstName" header="First Name"></d-column>
      <d-column field="lastName" header="Last Name"></d-column>
    </d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    return { dataSource };
  },
});
</script>

:::

열 정렬

:::demo sortable 파라미터를 true로 설정하면 열 정렬을 지원해요. sort-direction은 초기 정렬 방식을 정하고, sort-method는 각 열의 정렬 방법을 정의해요. sort-change는 정렬 콜백 이벤트로, 해당 열의 정렬 정보인 field(정렬 필드)와 direction(정렬 방향)을 반환해요.

<template>
  <d-table :data="dataSource" @sort-change="handleSortChange">
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name" sortable :sort-method="sortNameMethod"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth" sortable sort-direction="ASC" :sort-method="sortDateMethod"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const dataSource = ref([
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Female',
        date: '1990/01/13',
      },
      {
        firstName: 'Green',
        lastName: 'Gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);
    const handleSortChange = ({ field, direction }) => {
      console.log('field', field);
      console.log('direction', direction);
    };
    const sortDateMethod = (a, b) => {
      return a.date > b.date;
    };
    const sortNameMethod = (a, b) => {
      return a.lastName > b.lastName;
    };

    return { dataSource, handleSortChange, sortDateMethod, sortNameMethod };
  },
});
</script>

:::

열 필터

:::demo filterable 파라미터를 true로 설정하면 열 필터를 지원해요. filter-multiple은 필터 목록을 여러 개 선택할 수 있는지 정하며 기본값은 true예요. filter-list로 필터 목록을 정의하고, filter-change는 필터 콜백 이벤트로 해당 열에서 선택된 데이터를 반환해요. 단일 선택일 때는 선택 항목 하나를, 다중 선택일 때는 선택 항목 배열을 돌려 줘요.

<template>
  <d-table :data="dataSource">
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name" filterable :filter-list="filterList" @filter-change="handleFilterChange"></d-column>
    <d-column
      field="gender"
      header="Gender"
      filterable
      :filter-multiple="false"
      :filter-list="singleFilterList"
      @filter-change="handleSingleChange"
    ></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const originSource = ref([
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Female',
        date: '1990/01/13',
      },
      {
        firstName: 'Green',
        lastName: 'Gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);
    const dataSource = ref([...originSource.value]);
    const filterList = dataSource.value.map((item) => {
      return { name: item.lastName, value: item.lastName };
    });
    const singleFilterList = [
      {
        name: 'Clear',
        value: 'Clear',
      },
      {
        name: 'Female',
        value: 'Female',
      },
      {
        name: 'Male',
        value: 'Male',
      },
    ];
    const handleFilterChange = (val) => {
      const filterList = val.map((item) => item.value);
      dataSource.value = originSource.value.filter((item) => filterList.includes(item.lastName));
    };
    const handleSingleChange = (val) => {
      if (val.value === 'Clear') {
        dataSource.value = originSource.value;
        return;
      }
      dataSource.value = originSource.value.filter((item) => item.gender === val.value);
    };

    return { dataSource, filterList, singleFilterList, handleFilterChange, handleSingleChange };
  },
});
</script>

:::

행 펼치기

:::demo

<template>
  <div>
    <d-button @click="toggleRowExpansion" class="mr-m mb-m">toggleRowExpansion</d-button>
    <d-table ref="tableRef" :data="dataSource" row-key="id" :expand-row-keys="['1', '3']" @expand-change="expandChange">
      <d-column type="expand">
        <template #default="rowData">
          <div>First Name: {{ rowData.row.firstName }}</div>
          <div>Last Name: {{ rowData.row.lastName }}</div>
          <div>Gender: {{ rowData.row.gender }}</div>
          <div>Date of birth: {{ rowData.row.date }}</div>
        </template>
      </d-column>
      <d-column field="firstName" header="First Name"></d-column>
      <d-column field="lastName" header="Last Name"></d-column>
      <d-column field="gender" header="Gender"></d-column>
      <d-column field="date" header="Date of birth"></d-column>
    </d-table>
  </div>
</template>

<script>
import { defineComponent, ref, onMounted } from 'vue';

export default defineComponent({
  setup() {
    const tableRef = ref();

    const dataSource = ref([
      {
        id: '1',
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        id: '2',
        firstName: 'Mark',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        id: '3',
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Female',
        date: '1990/01/13',
      },
      {
        id: '4',
        firstName: 'Green',
        lastName: 'Gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    /* onMounted(() => {
      tableRef.value.store.expandRow(dataSource.value[0]);
    }); */

    const expandChange = (currentRow, expandedRows) => {
      console.log('currentRow', currentRow);
      console.log('expandedRows', expandedRows);
    };

    const toggleRowExpansion = () => {
      tableRef.value.store.toggleRowExpansion(dataSource.value[0]);
    };

    return { dataSource, expandChange, tableRef, toggleRowExpansion };
  },
});
</script>

:::

트리 표

:::demo 트리 형태의 데이터 표시를 지원해요. row에 children 필드가 있으면 트리 데이터로 취급해요. 중첩 데이터를 렌더링하려면 row-key가 필요해요. indent로 자식 노드의 들여쓰기를 조절할 수 있어요. 아직 트리 표와 행 펼치기를 동시에 사용하는 건 지원하지 않고, 행 펼치기 쪽 우선순위가 높아요.

<template>
  <d-table :indent="32" @check-change="treeCheckChange" :data="baseTreeTableData" row-key="firstName">
    <d-column type="index">
      <template #default="scope">
        {{ `No.${scope.rowIndex + 1}` }}
      </template>
    </d-column>
    <d-column field="firstName" header="First Name" show-overflow-tooltip></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const baseTreeTableData = ref([
      {
        firstName: 'Mark1',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male1',
        children: [
          {
            firstName: 'Mark2',
            lastName: 'Otto',
            date: '1990/01/11',
            gender: 'Male',
          },
          {
            firstName: 'Mark3',
            lastName: 'Otto',
            date: '1990/01/11',
            gender: 'Male',
            children: [
              {
                firstName: 'Mark31',
                lastName: 'Otto',
                date: '1990/01/11',
                gender: 'Male',
              },
              {
                firstName: 'Mark32',
                lastName: 'Otto',
                date: '1990/01/11',
                gender: 'Male',
              },
            ],
          },
        ],
      },
      {
        firstName: 'Jacob',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
        children: [
          {
            firstName: 'Jacob2',
            lastName: 'Otto',
            date: '1990/01/11',
            gender: 'Male',
          },
          {
            firstName: 'Jacob3',
            lastName: 'Otto',
            date: '1990/01/11',
            gender: 'Male',
            children: [
              {
                firstName: 'Jacob31',
                lastName: 'Otto',
                date: '1990/01/11',
                gender: 'Male',
              },
              {
                firstName: 'Jacob32',
                lastName: 'Otto',
                date: '1990/01/11',
                gender: 'Male',
              },
            ],
          },
        ],
      },
      {
        firstName: 'Danni',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'green',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
    ]);

    const treeCheckChange = (val, row, selection) => {
      console.log('treeCheckChange', selection);
    };

    return { baseTreeTableData, treeCheckChange };
  },
});
</script>

:::

지연 로딩

:::demo lazy를 사용해 지연 로딩을 켜고, 표 맨 아래로 스크롤하면 loadMore 이벤트가 발생하면서 지연 로딩이 이루어져요.

<template>
  <d-table :data="dataSource" table-height="200px" :show-loading="showLoading" :lazy="true" @load-more="loadMore">
    <d-column field="firstName" header="First Name"></d-column>
    <d-column field="lastName" header="Last Name"></d-column>
    <d-column field="gender" header="Gender"></d-column>
    <d-column field="date" header="Date of birth"></d-column>
  </d-table>
</template>

<script>
import { defineComponent, ref } from 'vue';

export default defineComponent({
  setup() {
    const showLoading = ref(false);
    const dataSource = ref([
      {
        firstName: 'diy0',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'diy1',
        lastName: 'Otto',
        date: '1990/01/11',
        gender: 'Male',
      },
      {
        firstName: 'diy2',
        lastName: 'Thornton',
        gender: 'Female',
        date: '1990/01/12',
      },
      {
        firstName: 'diy3',
        lastName: 'Chen',
        gender: 'Male',
        date: '1990/01/13',
      },
      {
        firstName: 'diy4',
        lastName: 'gerong',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'diy5',
        lastName: 'lang',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'diy6',
        lastName: 'li',
        gender: 'Male',
        date: '1990/01/14',
      },
      {
        firstName: 'diy7',
        lastName: 'li',
        gender: 'Female',
        date: '1990/01/14',
      },
      {
        firstName: 'diy8',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/14',
      },
      {
        firstName: 'diy9',
        lastName: 'Yu',
        gender: 'Female',
        date: '1990/01/14',
      },
    ]);

    let total = 100;

    const loadMore = () => {
      if (dataSource.value.length >= total || showLoading.value) {
        return;
      }

      showLoading.value = true;
      const moreData = [];
      const size = dataSource.value.length;
      for (let i = 0; i < 10; i++) {
        moreData.push({
          firstName: 'diy' + (i + size),
          lastName: 'more data',
          gender: 'Female',
          date: '2022/07/20',
        });
      }

      // mock ajax
      setTimeout(() => {
        showLoading.value = false;
        dataSource.value = dataSource.value.concat(moreData);
      }, 500);
    };

    return { dataSource, loadMore, showLoading };
  },
});
</script>

:::

Table 파라미터

파라미터 타입 기본값 설명 Demo 링크
data array [] 선택, 표시할 데이터 기본 사용법
striped boolean false 선택, 줄무늬 행 간격 표시 여부 표 스타일
size TableSize 'sm' 선택, 표 크기, 행 높이 40px, 48px, 56px에 각각 대응 표 스타일
max-width string -- 선택, 표 최대 너비
max-height boolean -- 선택, 표 최대 높이
table-width string -- 선택, 표 너비
table-height string -- 선택, 표 높이
row-hovered-highlight boolean true 선택, 마우스가 해당 행 위에 있을 때 행을 하이라이트 표 스타일
fix-header boolean false 선택, 헤더 고정 고정 표헤더
show-loading boolean false 선택, 로딩 애니메이션 표시 빈 데이터 템플릿
header-bg boolean false 선택, 헤더 배경 표 스타일
table-layout 'fixed' | 'auto' 'fixed' 선택, 표 레이아웃, 선택 값은 'auto' 표 스타일
span-method SpanMethod -- 선택, 셀 병합 계산 메서드 셀 병합
border-type BorderType '' 선택, 표 테두리 타입, 기본은 행 테두리; bordered: 전체 테두리; borderless: 테두리 없음 표 스타일
empty string 'No Data' 선택, 표 데이터를 넘겨주지 않았을 때 표시할 빈 데이터 텍스트 빈 데이터 템플릿
show-header boolean true 선택, 표헤더 표시 여부 설정 표 스타일
row-key string | Function(item, index: number): string -- 선택, 행 데이터의 Key. Table 렌더링 최적화에 쓰여요. string 타입일 때는 item.user.id처럼 여러 층 접근을 지원하지만 item.user[0].id는 지원하지 않아요. 이런 경우는 Function을 사용하세요. 표 인터랙션(Function)
행 펼치기(string)
indent number 16 선택, 트리 데이터를 표시할 때 트리 노드의 들여쓰기 트리 표
lazy boolean false 선택, 데이터 지연 로딩 여부 (loadMore와 함께 사용) 지연 로딩

Table 이벤트

이벤트명 콜백 파라미터 설명 Demo 링크
sort-change Function(obj: { field: string; direction: SortDirection }) 정렬 콜백 이벤트, 해당 열의 정렬 정보를 반환 열 정렬
cell-click Function(obj: CellClickArg) 셀 클릭 이벤트, 셀 정보를 반환 표 인터랙션
check-change Function(checked: boolean, row, selection) 표 행 체크 콜백 이벤트, 해당 행 정보와 표의 모든 선택된 행 데이터를 반환 표 인터랙션
check-all-change Function(checked: boolean, selection) 표 행 전체 선택 콜백 이벤트, 체크 상태와 표의 모든 선택된 행 데이터를 반환 표 인터랙션
row-click Function(obj: RowClickArg) 특정 행이 클릭될 때 발생하는 이벤트, 해당 행 정보를 반환 표 인터랙션
load-more Function() 표 맨 아래로 스크롤하면 발생하는 지연 로딩 이벤트 (props.lazy를 켜야 동작) 지연 로딩

Table 메서드

메서드명 타입 설명
getCheckedRows () => [] 현재 선택된 행 데이터를 가져와요
toggleRowExpansion (row, expended) => void 펼칠 수 있는 표에서 특정 행의 펼침 상태를 토글해요. 두 번째 인자를 사용하면 그 행을 표시할지 설정해요 (expended가 true면 펼쳐짐)
toggleRowSelection (row, checked) => void 선택할 수 있는 표에서 특정 행의 선택 상태를 토글해요. 두 번째 인자를 사용하면 그 행을 선택할지 설정해요 (checked가 true면 선택됨)
setCellMode (row, rowIndex, field, cellMode) => void 편집 가능한 셀을 가진 표에서 사용해요. cellMode 파라미터: readonly는 읽기 전용 상태, edit는 편집 상태이며 셀은 상태에 따라 다른 내용을 렌더링해요
resetCellMode () => void 편집 가능한 셀을 가진 표에서 모든 편집 가능 셀을 읽기 전용 상태로 되돌려요

Table 슬롯

슬롯명 설명 파라미터
empty 표 데이터를 넘겨주지 않았을 때 표시할 빈 데이터 템플릿

Column 파라미터

파라미터 타입 기본값 설명 Demo 링크
header string -- 선택, 해당 열의 제목 기본 사용법
field string -- 선택, 해당 열 내용의 필드 이름 기본 사용법
type ColumnType '' 선택, 열의 타입, checkable을 설정하면 다중 선택 박스가 표시돼요 표 인터랙션
width string | number -- 선택, 해당 열의 너비, 단위 px
min-width string | number -- 선택, 드래그로 너비를 조정할 때의 최소 너비, 단위 px
max-width string | number -- 선택, 드래그로 너비를 조정할 때의 최대 너비, 단위 px
fixedLeft string -- 선택, 해당 열을 왼쪽에 고정할 거리, 예: '100px' 고정 열
fixedRight string -- 선택, 해당 열을 오른쪽에 고정할 거리, 예: '100px' 고정 열
formatter Formatter -- 선택, 열 내용 포매팅
sortable boolean false 선택, 행 데이터를 해당 열 순서대로 정렬 열 정렬
sort-direction SortDirection '' 선택, 해당 열의 정렬 상태 설정 열 정렬
sort-method SortMethod -- 선택, 정렬에 사용할 비교 함수 열 정렬
filterable boolean false 선택, 해당 열에 필터 기능을 켤지 여부 열 필터
filter-multiple boolean true 선택, 다중 선택 방식의 필터를 켤지 여부 열 필터
filter-list FilterConfig[] [] 선택, 필터 목록 열 필터
align ColumnAlign 'left' 선택, 수평 정렬 방식 설정 사용자 지정 열
checkable Function(row, rowIndex): boolean -- 선택, 행 체크 상태 설정 표 인터랙션
show-overflow-tooltip boolean false 선택, 내용이 길어 숨겨졌을 때 tooltip 표시 여부
resizeable boolean false 선택, 해당 열의 너비를 조정할 수 있는지
reserve-check boolean false 선택, 체크 상태를 유지할지 여부 표 인터랙션
cell-class string '' 선택, 해당 열 셀의 사용자 지정 class

Column 이벤트

이벤트명 콜백 파라미터 설명 Demo 링크
filter-change Function(val: FilterConfig | FilterConfig[]) 필터 콜백 이벤트, 선택된 필터 항목 또는 필터 항목 배열을 반환 열 필터
resize-start Function() 해당 열 너비 조정이 시작될 때의 이벤트 표 인터랙션
resizing Function(val: {width: number}) 해당 열 너비 조정이 진행 중일 때의 이벤트 표 인터랙션
resize-end Function(val: {width: number; beforeWidth: number}) 해당 열 너비 조정이 끝났을 때의 이벤트 표 인터랙션

Column 슬롯

슬롯명 설명 파라미터
default 기본 슬롯, 열 내용을 직접 꾸며요 { row, rowIndex }
header 표헤더 슬롯, 표헤더 내용을 직접 꾸며요
cell 내용 읽기 전용 상태 슬롯 (편집 가능 셀 기능과 함께 사용), 열 내용을 직접 꾸며요 { row, rowIndex }
cellEdit 내용 편집 상태 슬롯 (편집 가능 셀 기능과 함께 사용), 열 내용을 직접 꾸며요 { row, rowIndex }

Table 타입 정의


TableSize

type TableSize = 'sm' | 'md' | 'lg';

SpanMethod

type SpanMethod = (data: {
  row: any;
  column: any;
  rowIndex: number;
  columnIndex: number;
}) => number[] | { rowspan: number; colspan: number };

BorderType

type BorderType = '' | 'bordered' | 'borderless';

CellClickArg

interface CellClickArg {
  columnIndex: number;
  rowIndex: number;
  column: Column;
  row: DefaultRow;
}

RowClickArg

interface RowClickArg {
  row: DefaultRow;
}

Column 타입 정의


ColumnType

type ColumnType = 'checkable' | 'index' | 'expand' | 'editable' | '';

Formatter

type Formatter = (row: any, column: any, cellValue: any, rowIndex: number) => VNode;

SortDirection

type SortDirection = 'ASC' | 'DESC' | '';

SortMethod

type SortMethod<T = any> = (a: T, b: T) => boolean;

FilterConfig

interface FilterConfig {
  name: string;
  value: any;
  checked?: boolean;
}

ColumnAlign

type ColumnAlign = 'left' | 'center' | 'right';

더 알아보기 (Learn more)

d-table은 기본 표부터 셀 편집, 트리 데이터, 지연 로딩까지 실무에서 자주 쓰는 표 기능을 대부분 갖추고 있어요. 공식 문서의 데모를 하나씩 따라 해 보면 각 기능이 어떤 파라미터와 연결되는지 금방 감을 잡을 수 있을 거예요.