Skip to content

BaseTable ​

재사용 가능한 기본 테이블 컴포넌트입니다. 정렬, 페이징, 컬럼 크기 조정, 행 선택 등의 기본 테이블 기능을 제공합니다.

인터랙티브 데모 ​

헤더를 눌러 정렬하고 행을 선택하세요. BaseTable의 row 슬롯에 BaseRow를 연결한 예입니다.

개요 ​

BaseTable은 PagingTable의 핵심 기능을 담당하는 컴포넌트로, 대용량 데이터 테이블을 효율적으로 렌더링하고 사용자 인터랙션을 처리합니다. 컬럼 설정 저장, 동적 크기 조정, 스켈레톤 로딩 등의 고급 기능을 포함합니다.

주요 특징 ​

  • 📊 대용량 데이터 처리: 가상화를 통한 효율적인 렌더링
  • 🔧 컬럼 커스터마이징: 크기 조정, 표시/숨김, 순서 변경
  • 🔍 정렬 및 검색: 다중 컬럼 정렬과 행 검색 지원
  • 💾 설정 저장: 로컬 스토리지를 통한 사용자 설정 유지
  • ⚡ 스켈레톤 로딩: 데이터 로딩 중 스켈레톤 UI 표시
  • ✅ 행 선택: 단일/다중 행 선택 지원

Props Interface ​

typescript
interface PagingTableProps<T extends Record<string, unknown>> {
  /** 테이블 고유 식별자 (설정 저장용) */
  alias: string;
  
  /** 테이블 너비 */
  width: number;
  
  /** 테이블 높이 */
  height: number;
  
  /** 페이지당 표시할 행 수 */
  displayCount: number;
  
  /** 컬럼 정의 배열 */
  columns: PagingTableColumn[];
  
  /** 테이블 데이터 배열 */
  rows: PagingTableRow<T>[];
  
  /** 행 높이 (기본값: 40px) */
  rowHeight?: number;
  
  /** 컬럼별 최대값 맵 (프로그레스 바 등에 사용) */
  maxMap?: Record<string, number>;
  
  /** 현재 정렬 키 */
  sortKey?: string;
  
  /** 현재 정렬 순서 */
  sortOrder?: PagingTableSortOrder;
  
  /** 정렬 로딩 상태 */
  sortLoading?: boolean;
  
  /** 스켈레톤 로딩 사용 여부 (기본값: true) */
  useSkeleton?: boolean;
  
  /** 행 선택 기능 사용 여부 (기본값: true) */
  useSelect?: boolean;
  
  /** 찾을 행 ID (해당 행으로 자동 스크롤) */
  foundRowId?: string;
  
  /** 선택된 행 ID 배열 */
  selectedRowIds?: string[];
}

컬럼 정의 ​

typescript
interface PagingTableColumn {
  /** 컬럼 키 (데이터 객체의 속성 이름) */
  key: string;
  
  /** 컬럼 표시 이름 */
  name: string;
  
  /** 셀 타입 */
  type: PagingTableCellType;
  
  /** 컬럼 비율 (0-1, 전체 너비 대비) */
  rate?: number;
  
  /** 정렬 가능 여부 */
  sortable?: boolean;
  
  /** 표시 여부 */
  visible?: boolean;
  
  /** 셀 색상 */
  color?: string;
}

행 데이터 구조 ​

typescript
interface PagingTableRow<T> {
  /** 실제 데이터 객체 */
  data: T;
  
  /** 행 고유 식별자 */
  id: string;
}

Events ​

typescript
interface PagingTableEmits<T> {
  /** 컬럼 정렬 시 발생 */
  'sort': [key: string, order: PagingTableSortOrder];
  
  /** 행 선택 시 발생 */
  'select': [id: string, row?: PagingTableRow<T>];
}

기본 사용법 ​

vue
<template>
  <BaseTable
    alias="transaction-table"
    :width="800"
    :height="600"
    :display-count="50"
    :columns="tableColumns"
    :rows="tableRows"
    :sort-key="sortKey"
    :sort-order="sortOrder"
    :selected-row-ids="selectedRows"
    @sort="handleSort"
    @select="handleRowSelect"
  >
    <template #information>
      <div class="table-info">
        총 {{ tableRows.length }}개 항목
      </div>
    </template>
    
    <template #tools="{ columns, updateColumns, resetSettings }">
      <div class="table-tools">
        <button @click="resetSettings">설정 초기화</button>
        <button @click="exportData">데이터 내보내기</button>
      </div>
    </template>
    
    <template #row="{ row, column, value }">
      <!-- 커스텀 셀 렌더링 -->
      <span v-if="column.key === 'status'" :class="`status-${value}`">
        {{ getStatusText(value) }}
      </span>
      <span v-else>{{ value }}</span>
    </template>
  </BaseTable>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { BaseTable } from '@jennifersoft/apm-components';
import type { 
  PagingTableColumn, 
  PagingTableRow, 
  PagingTableSortOrder 
} from '@jennifersoft/apm-components';

interface TransactionData {
  id: string;
  timestamp: number;
  method: string;
  uri: string;
  responseTime: number;
  status: number;
  errorCount: number;
}

const tableColumns = ref<PagingTableColumn[]>([
  {
    key: 'timestamp',
    name: '시간',
    type: PagingTableCellType.DATETIME,
    rate: 0.15,
    sortable: true
  },
  {
    key: 'method',
    name: 'Method',
    type: PagingTableCellType.TEXT,
    rate: 0.1,
    sortable: true
  },
  {
    key: 'uri',
    name: 'URI',
    type: PagingTableCellType.TEXT,
    rate: 0.4,
    sortable: true
  },
  {
    key: 'responseTime',
    name: '응답시간',
    type: PagingTableCellType.NUMBER,
    rate: 0.15,
    sortable: true
  },
  {
    key: 'status',
    name: '상태',
    type: PagingTableCellType.CUSTOM,
    rate: 0.1,
    sortable: true
  },
  {
    key: 'errorCount',
    name: '에러수',
    type: PagingTableCellType.NUMBER,
    rate: 0.1,
    sortable: true
  }
]);

const tableRows = ref<PagingTableRow<TransactionData>[]>([]);
const sortKey = ref<string>('timestamp');
const sortOrder = ref<PagingTableSortOrder>(PagingTableSortOrder.DESC);
const selectedRows = ref<string[]>([]);

const handleSort = (key: string, order: PagingTableSortOrder) => {
  sortKey.value = key;
  sortOrder.value = order;
  
  // 실제 정렬 로직 구현
  sortTableData(key, order);
};

const handleRowSelect = (id: string, row?: PagingTableRow<TransactionData>) => {
  if (selectedRows.value.includes(id)) {
    selectedRows.value = selectedRows.value.filter(rowId => rowId !== id);
  } else {
    selectedRows.value.push(id);
  }
};

const getStatusText = (status: number): string => {
  if (status >= 200 && status < 300) return '성공';
  if (status >= 400 && status < 500) return '클라이언트 오류';
  if (status >= 500) return '서버 오류';
  return '알 수 없음';
};
</script>

컬럼 타입 ​

BaseTable은 다양한 셀 타입을 지원합니다:

typescript
enum PagingTableCellType {
  TEXT = 'TEXT',           // 일반 텍스트
  NUMBER = 'NUMBER',       // 숫자 (천 단위 구분자 포함)
  DATETIME = 'DATETIME',   // 날짜/시간
  PROGRESS = 'PROGRESS',   // 프로그레스 바
  CUSTOM = 'CUSTOM',       // 커스텀 렌더링
  LINK = 'LINK',          // 링크
  BADGE = 'BADGE',        // 배지
  CHART = 'CHART'         // 미니 차트
}

설정 저장 및 복원 ​

BaseTable은 사용자 설정을 자동으로 로컬 스토리지에 저장합니다:

typescript
// 저장되는 설정들
interface TableSettings {
  columnRates: number[];      // 컬럼 너비 비율
  visibleColumns: string[];   // 표시되는 컬럼 키
  sortKey: string;           // 마지막 정렬 키
  sortOrder: PagingTableSortOrder; // 마지막 정렬 순서
}

// 스토리지 키 형식
const storageKeys = {
  columnRates: `COLUMN_RATES#${alias}`,
  visibleColumns: `VISIBLE_COLUMNS#${alias}`,
  sortSettings: `SORT_SETTINGS#${alias}`
};

슬롯 활용 ​

information 슬롯 ​

vue
<template #information>
  <div class="table-summary">
    <span class="total-count">총 {{ totalCount }}개</span>
    <span class="selected-count" v-if="selectedCount > 0">
      {{ selectedCount }}개 선택됨
    </span>
    <span class="filter-status" v-if="isFiltered">
      필터 적용됨
    </span>
  </div>
</template>

tools 슬롯 ​

vue
<template #tools="{ columns, updateColumns, resetSettings }">
  <div class="table-toolbar">
    <ColumnManager 
      :columns="columns"
      @update="updateColumns"
    />
    <ExportButton 
      :data="tableRows"
      :columns="columns"
    />
    <button @click="resetSettings">
      설정 초기화
    </button>
  </div>
</template>

row 슬롯 ​

vue
<template #row="{ row, column, value, cellWidth }">
  <!-- 커스텀 셀 렌더링 -->
  <div 
    v-if="column.type === 'CUSTOM'"
    :style="{ width: cellWidth + 'px' }"
  >
    <StatusBadge :status="value" />
  </div>
  
  <!-- 기본 렌더링 -->
  <span v-else>{{ formatCellValue(value, column.type) }}</span>
</template>

의존성 ​

  • @jennifersoft/vue-components-v2: ProgressIndicator 컴포넌트
  • Vue 3: Composition API와 Generic Props 지원
  • 로컬 스토리지: 사용자 설정 저장

브라우저 지원 ​

  • Chrome 80+
  • Firefox 75+
  • Safari 13+
  • Edge 80+

알려진 제한사항 ​

  1. 메모리 사용량: 대용량 데이터 (10,000+ 행)에서 메모리 사용량 증가
  2. 가상화: 현재 수직 가상화만 지원 (수평 가상화 미지원)
  3. 컬럼 순서: 동적 컬럼 순서 변경 미지원 (향후 버전에서 지원 예정)