리스트 고급 기능¶
게시판 목록 페이지의 고급 기능들을 구현하는 방법을 학습합니다.
IP 주소 출력¶
글쓴이 IP 표시¶
<!-- 게시판 목록에서 IP 출력 -->
<td class="author">
{$document->getNickName()}
<span class="ip-address" cond="$grant->manager">
({$document->getIpaddress()})
</span>
</td>
<!-- IP 일부만 표시 -->
<td class="author">
{$document->getNickName()}
{@
$ip_parts = explode('.', $document->getIpaddress());
$masked_ip = $ip_parts[0] . '.' . $ip_parts[1] . '.*.*';
}
<span class="ip-address" cond="$logged_info->is_admin == 'Y'">
({$masked_ip})
</span>
</td>
프로필 이미지 출력¶
기본 프로필 이미지¶
<!-- 프로필 이미지 출력 -->
<td class="author">
{@
$member_srl = $document->get('member_srl');
if($member_srl) {
$profile_image = getModel('member')->getProfileImage($member_srl);
} else {
$profile_image = null;
}
}
<img cond="$profile_image" src="{$profile_image->src}" alt="profile" class="profile-img" />
<img cond="!$profile_image" src="/modules/board/skins/default/img/no-profile.png" alt="no profile" class="profile-img" />
<span class="nickname">{$document->getNickName()}</span>
</td>
<style>
.profile-img {
width: 30px;
height: 30px;
border-radius: 50%;
vertical-align: middle;
margin-right: 5px;
}
</style>
첨부파일 아이콘 출력¶
첨부파일 여부 표시¶
<!-- 제목 옆에 첨부파일 아이콘 -->
<td class="title">
<a href="{getUrl('document_srl',$document->document_srl)}">
{$document->getTitle()}
<!-- 첨부파일 아이콘 -->
<i class="xi-paperclip" cond="$document->hasUploadedFiles()" title="첨부파일"></i>
<!-- 이미지 아이콘 -->
<i class="xi-image-o" cond="$document->thumbnailExists()" title="이미지"></i>
<!-- 동영상 아이콘 -->
{@
$has_video = false;
if($document->hasUploadedFiles()) {
$file_list = $document->getUploadedFiles();
foreach($file_list as $file) {
if(preg_match('/\.(mp4|avi|wmv|flv|mov)$/i', $file->source_filename)) {
$has_video = true;
break;
}
}
}
}
<i class="xi-video-camera" cond="$has_video" title="동영상"></i>
</a>
</td>
카테고리 필터링¶
카테고리별 출력 및 이동¶
<!-- 카테고리 선택 드롭다운 -->
<div class="category-filter">
<select onchange="location.href=this.value">
<option value="{getUrl('category','')}" selected="selected"|cond="!$category">전체 카테고리</option>
<option loop="$category_list=>$val" value="{getUrl('category',$val->category_srl)}" selected="selected"|cond="$category==$val->category_srl">
{str_repeat(" ", $val->depth)} {$val->title} ({$val->document_count})
</option>
</select>
</div>
<!-- 카테고리 태그 스타일 -->
<div class="category-tags">
<a href="{getUrl('category','')}" class="tag all"|cond="!$category">전체</a>
<a loop="$category_list=>$val" href="{getUrl('category',$val->category_srl)}" class="tag category-{$val->category_srl}"|cond="$category==$val->category_srl">
{$val->title} <span class="count">({$val->document_count})</span>
</a>
</div>
추천수 기반 필터¶
추천수 N개 이상만 표시¶
<!-- 추천수 필터 옵션 -->
<div class="filter-options">
<label>
<input type="checkbox" id="filter-voted" /> 추천 5개 이상만 보기
</label>
</div>
<!-- JavaScript 필터링 -->
<script>
jQuery(function($) {
$('#filter-voted').change(function() {
if($(this).is(':checked')) {
$('.document-item').each(function() {
var voted = parseInt($(this).data('voted'));
if(voted < 5) {
$(this).hide();
}
});
} else {
$('.document-item').show();
}
});
});
</script>
<!-- 서버사이드 필터링 -->
{@
// 모듈 설정에서 최소 추천수 가져오기
$min_voted_count = $module_info->min_voted_count ?: 0;
// 추천수 필터 적용
if($min_voted_count > 0) {
$filtered_list = array();
foreach($document_list as $key => $document) {
if($document->get('voted_count') >= $min_voted_count) {
$filtered_list[] = $document;
}
}
$document_list = $filtered_list;
}
}
친구 여부 표시¶
친구 관계 확인¶
<!-- 친구 여부 확인 및 표시 -->
{@
$oMemberModel = getModel('member');
$friend_list = array();
if($logged_info) {
// 친구 목록 가져오기
$friend_output = executeQuery('member.getFriendList', (object)array('member_srl' => $logged_info->member_srl));
if($friend_output->data) {
foreach($friend_output->data as $friend) {
$friend_list[$friend->target_srl] = true;
}
}
}
}
<td class="author">
{@$is_friend = isset($friend_list[$document->get('member_srl')])}
<span class="friend-badge" cond="$is_friend">
<i class="xi-users"></i> 친구
</span>
{$document->getNickName()}
</td>
새글 아이콘 출력¶
24시간 이내 작성글 표시¶
<!-- 새글 아이콘 -->
{@
$new_time = 24 * 60 * 60; // 24시간
$is_new = (time() - ztime($document->get('regdate'))) < $new_time;
}
<td class="title">
<a href="{getUrl('document_srl',$document->document_srl)}">
<span class="new-badge" cond="$is_new">NEW</span>
{$document->getTitle()}
</a>
</td>
<!-- CSS -->
<style>
.new-badge {
display: inline-block;
padding: 2px 5px;
background: #ff4444;
color: white;
font-size: 10px;
border-radius: 3px;
margin-right: 5px;
animation: blink 1s ease-in-out infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
리스트 순서 변경¶
list_order 활용¶
<!-- 정렬 옵션 -->
<div class="sort-options">
<a href="{getUrl('sort_index','list_order','order_type','asc')}" class="active"|cond="$sort_index=='list_order'">
기본순
</a>
<a href="{getUrl('sort_index','regdate','order_type','desc')}" class="active"|cond="$sort_index=='regdate'">
최신순
</a>
<a href="{getUrl('sort_index','voted_count','order_type','desc')}" class="active"|cond="$sort_index=='voted_count'">
추천순
</a>
<a href="{getUrl('sort_index','readed_count','order_type','desc')}" class="active"|cond="$sort_index=='readed_count'">
조회순
</a>
<a href="{getUrl('sort_index','comment_count','order_type','desc')}" class="active"|cond="$sort_index=='comment_count'">
댓글순
</a>
</div>
<!-- 커스텀 정렬 -->
{@
// 가중치 계산하여 정렬
foreach($document_list as $key => $document) {
$weight = 0;
$weight += $document->get('voted_count') * 10;
$weight += $document->get('comment_count') * 5;
$weight += $document->get('readed_count') / 10;
$document->add('custom_weight', $weight);
}
// 가중치로 정렬
if($sort_index == 'custom') {
usort($document_list, function($a, $b) {
return $b->get('custom_weight') - $a->get('custom_weight');
});
}
}
태그 출력¶
리스트에서 태그 표시¶
<!-- 태그 표시 -->
<td class="tags">
{@$tag_list = $document->get('tag_list')}
<div class="tag-list" cond="$tag_list">
<i class="xi-tags"></i>
<a loop="$tag_list=>$tag" href="{getUrl('search_target','tag','search_keyword',$tag)}" class="tag-item">
#{$tag}
</a>
</div>
</td>
<!-- 인기 태그 클라우드 -->
<div class="tag-cloud" cond="$mi->use_tag=='Y'">
{@
// 태그 통계 가져오기
$tag_model = getModel('tag');
$tag_list = $tag_model->getModuleTags($module_info->module_srl, 20);
}
<div loop="$tag_list=>$tag" class="cloud-tag" style="font-size:{12+($tag->count/10)}px">
<a href="{getUrl('search_target','tag','search_keyword',$tag->tag)}">
{$tag->tag} ({$tag->count})
</a>
</div>
</div>
전체 게시물 수 출력¶
게시판 통계 표시¶
<!-- 게시판 통계 -->
<div class="board-stats">
<div class="stat-item">
<span class="label">전체 글</span>
<span class="value">{number_format($total_count)}</span>
</div>
<div class="stat-item">
<span class="label">오늘 글</span>
{@
$today_count = 0;
$today = date('Ymd');
foreach($document_list as $doc) {
if(substr($doc->get('regdate'), 0, 8) == $today) {
$today_count++;
}
}
}
<span class="value">{$today_count}</span>
</div>
<div class="stat-item">
<span class="label">전체 댓글</span>
{@
$comment_model = getModel('comment');
$total_comment_count = $comment_model->getCommentCountByModuleSrl($module_info->module_srl);
}
<span class="value">{number_format($total_comment_count)}</span>
</div>
</div>
폰트 크기 조정¶
사용자 폰트 크기 설정¶
<!-- 폰트 크기 조정 버튼 -->
<div class="font-size-control">
<button onclick="adjustFontSize(-1)" title="글자 작게">A-</button>
<button onclick="adjustFontSize(0)" title="기본 크기">A</button>
<button onclick="adjustFontSize(1)" title="글자 크게">A+</button>
</div>
<script>
// 폰트 크기 조정
var currentFontSize = 14;
function adjustFontSize(direction) {
if(direction === 0) {
currentFontSize = 14;
} else {
currentFontSize += direction * 2;
currentFontSize = Math.max(10, Math.min(20, currentFontSize));
}
jQuery('.board-list').css('font-size', currentFontSize + 'px');
// 쿠키에 저장
setCookie('board_font_size', currentFontSize, 30);
}
// 페이지 로드시 저장된 크기 적용
jQuery(function() {
var savedSize = getCookie('board_font_size');
if(savedSize) {
currentFontSize = parseInt(savedSize);
jQuery('.board-list').css('font-size', currentFontSize + 'px');
}
});
</script>
모범 사례¶
- 성능 고려: 불필요한 쿼리 최소화
- 캐싱 활용: 반복되는 데이터는 캐싱
- 반응형 디자인: 모바일 환경 고려
- 접근성: 스크린리더 지원
- 보안: 권한 체크 철저히
다음 단계¶
리스트 고급 기능을 구현했다면, 글 관리 기능을 학습하세요.