리스트 고급 기능

리스트 고급 기능

게시판 목록 페이지의 고급 기능들을 구현하는 방법을 학습합니다.

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("&nbsp;&nbsp;", $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>

모범 사례

  1. 성능 고려: 불필요한 쿼리 최소화
  2. 캐싱 활용: 반복되는 데이터는 캐싱
  3. 반응형 디자인: 모바일 환경 고려
  4. 접근성: 스크린리더 지원
  5. 보안: 권한 체크 철저히

다음 단계

리스트 고급 기능을 구현했다면, 글 관리 기능을 학습하세요.