vxe-grid 컴포넌트는 vxe-table을 기반으로 하여 테이블을 렌더링할 때, 기본 설정만으로는 충족되지 않는 복잡한 비즈니스 요구사항을 구현해야 할 경우 사용자 정의 템플릿을 통해 유연성을 극대화할 수 있습니다. vxe-grid는 슬롯(slot)을 이용한 템플릿 사용자 정의를 지원합니다.
셀 내용 사용자 정의
테이블 셀에 표시되는 내용을 동적으로 변경하거나 특정 컴포넌트를 삽입해야 할 때 셀 템플릿을 사용합니다. 각 셀은 자체적인 슬롯을 가지며, 이를 통해 해당 셀의 렌더링 방식을 정의할 수 있습니다.
<template>
<div>
<vxe-grid v-bind="gridOptions">
<!-- 이미지 URL을 vxe-image 컴포넌트로 렌더링 -->
<template #imageUrl_cell="{ row }">
<vxe-image :src="row.imageUrl" width="40" height="35" radius="4"></vxe-image>
</template>
<!-- 이름 필드 클릭 시 상세 정보 모달 호출 -->
<template #name_cell="{ row }">
<vxe-button mode="text" @click="showDetails(row)">{{ row.name }} 보기</vxe-button>
</template>
<!-- 성별 코드를 텍스트로 변환하여 표시 -->
<template #gender_cell="{ row }">
<span>{{ formatGender(row.gender) }}</span>
</template>
<!-- 주소 필드에 아이콘과 텍스트 조합 -->
<template #address_cell="{ row }">
<vxe-icon name="location" style="color: #1890ff;"></vxe-icon>
<span>{{ row.address }}</span>
<vxe-icon name="refresh" style="color: #f5222d;"></vxe-icon>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { VxeUI } from 'vxe-table' // 모달 메시지 표시를 위해 import
const gridOptions = reactive({
border: true,
showHeader: true,
columns: [
{ type: 'seq', width: 70, title: '번호' },
{ field: 'imageUrl', title: '사진', slots: { default: 'imageUrl_cell' } },
{ field: 'name', title: '이름', width: 150, slots: { default: 'name_cell' } },
{
title: '정보 그룹',
children: [
{ field: 'gender', title: '성별', slots: { default: 'gender_cell' } },
{ field: 'score', title: '점수', align: 'right' },
{ field: 'age', title: '나이', align: 'right' }
]
},
{ field: 'address', title: '주소', width: 250, slots: { default: 'address_cell' } }
],
data: [
{ id: 1, name: '김철수', gender: '1', age: 25, score: 95, imageUrl: 'https://vxeui.com/resource/img/fj577.jpg', address: '서울시 강남구' },
{ id: 2, name: '이영희', gender: '0', age: 30, score: 88, imageUrl: 'https://vxeui.com/resource/img/fj581.jpeg', address: '부산시 해운대구' },
{ id: 3, name: '박지성', gender: '1', age: 22, score: 92, imageUrl: 'https://vxeui.com/resource/img/fj581.jpeg', address: '대구시 수성구' }
]
})
// 성별 코드를 텍스트로 변환하는 함수
const formatGender = (genderCode) => {
return genderCode === '1' ? '남성' : '여성'
}
// 상세 정보 표시 함수
const showDetails = (row) => {
VxeUI.modal.message({
status: 'info',
content: `${row.name}님의 상세 정보를 표시합니다.`
})
}
</script>
헤더 내용 사용자 정의
테이블 헤더에 복잡한 구조나 인터랙티브 요소를 추가하고 싶을 때 헤더 템플릿을 사용합니다. 헤더 슬롯을 통해 헤더 셀의 내용을 완전히 대체할 수 있습니다.
<template>
<div>
<vxe-grid v-bind="gridOptions">
<!-- '이름' 헤더를 다중 라인으로 구성 -->
<template #header_columnName>
<div class="custom-header">
<div class="header-top">사용자</div>
<div class="header-bottom">이름</div>
</div>
</template>
<!-- '성별' 헤더에 검색 입력 필드 추가 -->
<template #header_gender>
<vxe-input v-model="genderFilter" placeholder="성별 검색" size="mini"></vxe-input>
</template>
<!-- '정보 그룹' 헤더에 스타일 적용 -->
<template #header_infoGroup>
<span style="color: #007bff; font-weight: bold;">개인 정보</span>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const genderFilter = ref('') // 성별 검색어
const gridOptions = reactive({
border: true,
columns: [
{ type: 'seq', width: 70, title: '순번' },
{ field: 'name', title: '이름', width: 180, slots: { header: 'header_columnName' } },
{
title: '정보 그룹',
field: 'infoGroup', // 그룹 필드 지정 (선택 사항)
slots: { header: 'header_infoGroup' },
children: [
{ field: 'gender', title: '성별', slots: { header: 'header_gender' } },
{ field: 'score', title: '점수', align: 'right' },
{ field: 'age', title: '나이', align: 'right' }
]
},
{ field: 'address', title: '주소' }
],
data: [
{ id: 1, name: '김철수', gender: '1', age: 25, score: 95, address: '서울시 강남구' },
{ id: 2, name: '이영희', gender: '0', age: 30, score: 88, address: '부산시 해운대구' },
{ id: 3, name: '박지성', gender: '1', age: 22, score: 92, address: '대구시 수성구' }
]
})
</script>
<style lang="scss" scoped>
.custom-header {
position: relative;
height: 25px;
.header-top {
position: absolute;
right: 5px;
top: -12px;
font-size: 10px;
color: #999;
}
.header-bottom {
position: absolute;
left: 5px;
bottom: -12px;
font-weight: bold;
}
</style>
푸터 내용 사용자 정의
테이블 하단에 요약 정보나 합계 등을 표시할 때 푸터 템플릿을 활용합니다. `showFooter: true` 옵션과 함께 사용되며, 특정 컬럼의 푸터 렌더링을 지정할 수 있습니다.
<template>
<div>
<vxe-grid v-bind="gridOptions" :footerData="footerData">
<!-- '점수' 컬럼의 푸터에 통화 기호와 함께 표시 -->
<template #score_footer="{ row }">
<span style="color: #00a870; font-weight: bold;">SUM: ¥{{ row.score }}</span>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { computed, reactive } from 'vue'
// 특정 필드의 합계를 계산하는 함수
const calculateSum = (list, fieldName) => {
let total = 0
if (list && list.length > 0) {
list.forEach(item => {
total += Number(item[fieldName] || 0)
})
}
return total
}
const gridOptions = reactive({
border: true,
showFooter: true, // 푸터 표시 활성화
columns: [
{ type: 'seq', width: 70, title: '순번' },
{ field: 'name', title: '이름' },
{ field: 'gender', title: '성별' },
{ field: 'score', title: '점수', align: 'right', slots: { footer: 'score_footer' } },
{ field: 'age', title: '나이', align: 'right' },
{ field: 'address', title: '주소' }
],
data: [
{ id: 1, name: '김철수', gender: '1', age: 25, score: 95, address: '서울시 강남구' },
{ id: 2, name: '이영희', gender: '0', age: 30, score: 88, address: '부산시 해운대구' },
{ id: 3, name: '박지성', gender: '1', age: 22, score: 92, address: '대구시 수성구' }
]
})
// 푸터 데이터를 계산하는 computed 속성
const footerData = computed(() => {
return [
{ name: '총 합계', score: calculateSum(gridOptions.data, 'score') }
]
})
</script>
편집 가능한 셀 템플릿
셀 편집 시 사용자 정의 컴포넌트를 사용하거나 편집 로직을 변경해야 할 때 편집 템플릿을 적용합니다. `editRender` 속성과 함께 `slots.edit`을 사용하여 편집 템플릿을 지정합니다.
<template>
<div>
<vxe-grid v-bind="gridOptions">
<!-- 성별 편집 시 vxe-select 사용 -->
<template #gender_editor="{ row }">
<vxe-select v-model="row.gender" :options="genderOptions"></vxe-select>
</template>
<!-- 성별 표시 템플릿 (편집 불가능할 때) -->
<template #gender_cell="{ row }">
<span>{{ formatGenderText(row.gender) }}</span>
</template>
<!-- 점수 편집 시 vxe-input 사용 (숫자만 입력 가능하도록) -->
<template #score_editor="{ row }">
<vxe-input v-model="row.score" type="number"></vxe-input>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const gridOptions = reactive({
border: true,
editConfig: { // 편집 설정
trigger: 'click', // 클릭 시 편집 모드 활성화
mode: 'cell' // 셀 단위 편집
},
columns: [
{ type: 'checkbox', width: 50 },
{ field: 'name', title: '이름' },
{
field: 'gender',
title: '성별',
editRender: {}, // 편집 렌더러 활성화
slots: { edit: 'gender_editor', default: 'gender_cell' } // 편집 및 기본 템플릿 지정
},
{
field: 'score',
title: '점수',
align: 'right',
editRender: {}, // 편집 렌더러 활성화
slots: { edit: 'score_editor' } // 편집 템플릿 지정
},
{ field: 'age', title: '나이', align: 'right' },
{ field: 'address', title: '주소' }
],
data: [
{ id: 1, name: '김철수', gender: '1', age: 25, score: 95, address: '서울시 강남구' },
{ id: 2, name: '이영희', gender: '0', age: 30, score: 88, address: '부산시 해운대구' },
{ id: 3, name: '박지성', gender: '1', age: 22, score: 92, address: '대구시 수성구' }
]
})
// 성별 옵션
const genderOptions = ref([
{ label: '남성', value: '1' },
{ label: '여성', value: '0' }
])
// 성별 코드를 텍스트로 변환하는 함수 (표시용)
const formatGenderText = (genderCode) => {
const option = genderOptions.value.find(item => item.value === genderCode)
return option ? option.label : '알 수 없음'
}
</script>