카테고리 관리¶
게시판의 카테고리 시스템을 효과적으로 활용하는 방법을 학습합니다.
카테고리 설정¶
기본 카테고리 구조¶
<!-- 카테고리 목록 출력 -->
<div class="category-list">
<ul>
<li><a href="{getUrl('category','')}" class="active"|cond="!$category">전체</a></li>
<li loop="$category_list=>$val">
<!-- 깊이에 따른 들여쓰기 -->
{@$indent = str_repeat(' ', $val->depth)}
<a href="{getUrl('category',$val->category_srl)}"
class="active"|cond="$category==$val->category_srl">
{$indent}{$val->title} ({$val->document_count})
</a>
<!-- 하위 카테고리가 있는 경우 -->
<ul cond="$val->childs">
<li loop="$val->childs=>$child">
<a href="{getUrl('category',$child->category_srl)}">
{$child->title} ({$child->document_count})
</a>
</li>
</ul>
</li>
</ul>
</div>
트리 구조 카테고리¶
<!-- 접기/펼치기 가능한 트리 구조 -->
<div class="category-tree">
<ul class="tree-root">
{@
// 카테고리를 트리 구조로 재구성
$tree = array();
foreach($category_list as $cat) {
if(!$cat->parent_srl) {
$tree[$cat->category_srl] = $cat;
$tree[$cat->category_srl]->children = array();
}
}
foreach($category_list as $cat) {
if($cat->parent_srl && isset($tree[$cat->parent_srl])) {
$tree[$cat->parent_srl]->children[] = $cat;
}
}
}
<li loop="$tree=>$cat" class="tree-item">
<div class="tree-node">
<span class="toggle" cond="count($cat->children)">
<i class="xi-angle-right"></i>
</span>
<a href="{getUrl('category',$cat->category_srl)}"
class="active"|cond="$category==$cat->category_srl">
<i class="xi-folder"></i>
{$cat->title}
<span class="count">({$cat->document_count})</span>
</a>
</div>
<ul class="tree-children" cond="count($cat->children)" style="display:none;">
<li loop="$cat->children=>$child" class="tree-item">
<div class="tree-node">
<a href="{getUrl('category',$child->category_srl)}">
<i class="xi-folder-open"></i>
{$child->title}
<span class="count">({$child->document_count})</span>
</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
<script>
jQuery(function($) {
// 트리 토글
$('.tree-node .toggle').click(function() {
var $li = $(this).closest('.tree-item');
var $children = $li.children('.tree-children');
var $icon = $(this).find('i');
$children.slideToggle(200);
$icon.toggleClass('xi-angle-right xi-angle-down');
});
// 현재 카테고리 자동 펼치기
$('.tree-node a.active').each(function() {
$(this).closest('.tree-item').parents('.tree-children').show();
$(this).closest('.tree-item').parents('.tree-item').find('.toggle i')
.removeClass('xi-angle-right').addClass('xi-angle-down');
});
});
</script>
카테고리별 스타일¶
카테고리별 색상 지정¶
<!-- 카테고리별 색상 적용 -->
{@
// 카테고리별 색상 정의
$category_colors = array(
'공지사항' => '#ff4444',
'자유게시판' => '#4CAF50',
'Q&A' => '#2196F3',
'자료실' => '#FF9800'
);
}
<style>
<!--@foreach($category_list as $cat)-->
{@
$color = $category_colors[$cat->title] ?: '#666';
}
.category-{$cat->category_srl} {
color: {$color};
border-left: 3px solid {$color};
}
.cat-badge-{$cat->category_srl} {
background-color: {$color};
color: white;
}
<!--@end-->
</style>
<!-- 게시글 목록에서 카테고리 뱃지 -->
<td class="title">
<span class="cat-badge cat-badge-{$document->get('category_srl')}" cond="$document->get('category_srl')">
{$category_list[$document->get('category_srl')]->title}
</span>
<a href="{getUrl('document_srl',$document->document_srl)}">
{$document->getTitle()}
</a>
</td>
카테고리 필터 위젯¶
드롭다운 필터¶
<!-- 카테고리 드롭다운 -->
<div class="category-dropdown">
<select id="category-select" onchange="filterByCategory(this.value)">
<option value="">전체 카테고리</option>
<optgroup loop="$category_list=>$cat" label="{$cat->title}" cond="!$cat->parent_srl">
<option value="{$cat->category_srl}" selected="selected"|cond="$category==$cat->category_srl">
{$cat->title} ({$cat->document_count})
</option>
<!-- 하위 카테고리 -->
<option loop="$category_list=>$sub" value="{$sub->category_srl}" cond="$sub->parent_srl==$cat->category_srl">
ㄴ {$sub->title} ({$sub->document_count})
</option>
</optgroup>
</select>
</div>
<script>
function filterByCategory(category_srl) {
var url = current_url.setQuery('category', category_srl);
location.href = url;
}
</script>
태그 클라우드 스타일¶
<!-- 카테고리 태그 클라우드 -->
<div class="category-cloud">
{@
// 최대/최소 문서 수 계산
$max_count = 0;
$min_count = 999999;
foreach($category_list as $cat) {
$max_count = max($max_count, $cat->document_count);
$min_count = min($min_count, $cat->document_count);
}
$range = $max_count - $min_count;
}
<a loop="$category_list=>$cat"
href="{getUrl('category',$cat->category_srl)}"
class="cloud-tag"
style="font-size: {12 + (($cat->document_count - $min_count) / $range * 20)}px">
{$cat->title}
</a>
</div>
<style>
.category-cloud {
text-align: center;
padding: 20px;
}
.cloud-tag {
display: inline-block;
margin: 5px;
padding: 5px 15px;
background: #f0f0f0;
border-radius: 20px;
text-decoration: none;
transition: all 0.3s;
}
.cloud-tag:hover {
background: #333;
color: white;
transform: scale(1.1);
}
</style>
카테고리별 권한 설정¶
카테고리별 접근 제한¶
<!-- 카테고리별 권한 체크 -->
{@
// 카테고리별 권한 설정 (관리자에서 설정한 값)
$category_grants = array(
'123' => array('list' => -1, 'view' => -1), // 회원만
'456' => array('list' => 1, 'view' => 1), // 모두
'789' => array('list' => -100, 'view' => -100) // 관리자만
);
$current_cat_grant = $category_grants[$category] ?: array('list' => 1, 'view' => 1);
// 권한 체크
$is_granted = true;
if($current_cat_grant['list'] == -1 && !$logged_info) {
$is_granted = false;
} elseif($current_cat_grant['list'] == -100 && $logged_info->is_admin != 'Y') {
$is_granted = false;
}
}
<div cond="!$is_granted" class="message error">
이 카테고리는 {$current_cat_grant['list'] == -1 ? '회원만' : '관리자만'} 접근할 수 있습니다.
</div>
<div cond="$is_granted">
<!-- 게시글 목록 표시 -->
</div>
카테고리 통계¶
카테고리별 통계 표시¶
<!-- 카테고리 통계 대시보드 -->
<div class="category-stats">
<h3>카테고리별 통계</h3>
<div class="stats-grid">
<div loop="$category_list=>$cat" class="stat-card">
<h4>{$cat->title}</h4>
{@
// 오늘 작성된 글 수 계산
$today = date('Ymd');
$today_count = 0;
$args = new stdClass();
$args->module_srl = $module_info->module_srl;
$args->category_srl = $cat->category_srl;
$args->regdate_more = $today . '000000';
$args->regdate_less = $today . '235959';
$output = executeQuery('board.getDocumentCount', $args);
$today_count = $output->data->count ?: 0;
}
<div class="stat-item">
<span class="label">전체 글</span>
<span class="value">{number_format($cat->document_count)}</span>
</div>
<div class="stat-item">
<span class="label">오늘 글</span>
<span class="value">{$today_count}</span>
</div>
<!-- 차트 -->
<div class="mini-chart" data-category="{$cat->category_srl}">
<canvas id="chart-{$cat->category_srl}"></canvas>
</div>
</div>
</div>
</div>
<script>
// 카테고리별 차트 생성
jQuery(function($) {
$('.mini-chart').each(function() {
var category_srl = $(this).data('category');
var canvas = $(this).find('canvas')[0];
// AJAX로 최근 7일 데이터 가져오기
exec_xml('board', 'getCategoryStats', {category_srl: category_srl}, function(ret) {
if(ret.data) {
createMiniChart(canvas, ret.data);
}
});
});
function createMiniChart(canvas, data) {
new Chart(canvas, {
type: 'line',
data: {
labels: data.labels,
datasets: [{
data: data.values,
borderColor: '#2196F3',
fill: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
legend: { display: false },
scales: {
xAxes: [{ display: false }],
yAxes: [{ display: false }]
}
}
});
}
});
</script>
카테고리 네비게이션¶
이전/다음 카테고리 이동¶
<!-- 카테고리 네비게이션 -->
<div class="category-navigation">
{@
// 현재 카테고리의 인덱스 찾기
$current_index = -1;
$category_array = array_values($category_list);
foreach($category_array as $idx => $cat) {
if($cat->category_srl == $category) {
$current_index = $idx;
break;
}
}
$prev_category = $current_index > 0 ? $category_array[$current_index - 1] : null;
$next_category = $current_index < count($category_array) - 1 ? $category_array[$current_index + 1] : null;
}
<a cond="$prev_category" href="{getUrl('category', $prev_category->category_srl)}" class="nav-prev">
<i class="xi-angle-left"></i> {$prev_category->title}
</a>
<span class="nav-current">
{$category_list[$category]->title}
</span>
<a cond="$next_category" href="{getUrl('category', $next_category->category_srl)}" class="nav-next">
{$next_category->title} <i class="xi-angle-right"></i>
</a>
</div>
카테고리 검색¶
카테고리 내 검색¶
<!-- 카테고리별 검색 -->
<form action="{getUrl()}" method="get" class="category-search">
<input type="hidden" name="vid" value="{$vid}" />
<input type="hidden" name="mid" value="{$mid}" />
<input type="hidden" name="category" value="{$category}" />
<div class="search-box">
<select name="search_target">
<option value="title_content">제목+내용</option>
<option value="title">제목</option>
<option value="content">내용</option>
<option value="user_name">작성자</option>
<option value="tag">태그</option>
</select>
<input type="text" name="search_keyword" value="{$search_keyword}" placeholder="검색어 입력" />
<button type="submit">
<i class="xi-search"></i>
{$category ? $category_list[$category]->title . '에서' : ''} 검색
</button>
</div>
</form>
모범 사례¶
- 계층 구조: 깊이 제한하여 복잡도 관리
- 네이밍: 명확하고 일관된 카테고리명 사용
- 권한 관리: 카테고리별 세밀한 권한 설정
- UI/UX: 직관적인 카테고리 네비게이션
- 성능: 카테고리 수가 많을 때 캐싱 활용
다음 단계¶
카테고리 관리를 구현했다면, 확장변수 활용을 학습하세요.