웹메신저 ui, html bootstrap4.x, jquery
by 개발자   2025-10-01 08:47:57   조회수:74

주요 기능 및 특징

  • 화면 구성: 최상단 로고 및 창 제어, 상단 메뉴(프로필, 대화, 상태), 좌측 탭 메뉴(조직도, 공지 등), 중앙 대화창으로 구성된 직관적인 UI

  • 실시간 메시징stomp.js 라이브러리를 활용하여 WebSocket을 통해 서버와 실시간으로 메시지를 주고받는 기능 구현

  • 동적 UI: jQuery를 사용하여 탭 메뉴 전환, 메시지 전송 및 수신 시 동적으로 화면을 갱신

  • 반응형 디자인: Bootstrap 4.x를 기반으로 하여 다양한 화면 크기에 대응 가능

  • 아이콘: Font Awesome 아이콘을 사용하여 메뉴 및 버튼의 시인성 향상

데모 화면

프론트엔드 코드 (HTML, CSS, JavaScript)

아래 코드는 하나의 HTML 파일(.html)으로 저장하여 바로 사용할 수 있습니다. 실제 환경에서는 CSS와 JavaScript를 별도 파일로 분리하여 관리하는 것을 권장합니다.

코드 설명 및 사용법

  1. 라이브러리 포함: Bootstrap 4, Font Awesome, jQuery, SockJS, Stomp.js 라이브러리를 CDN을 통해 포함시켰습니다.

  2. HTML 구조: 요청사항에 맞게 top-bartop-menumain-content로 구조를 나누고, main-content 내부에 left-tabs와 tab-content-area를 배치했습니다.

  3. CSS 스타일: 각 영역의 디자인과 조직도(ul, li 태그 활용) 스타일을 정의했습니다.

  4. JavaScript (STOMP 연동):

    • connect(): SockJS를 사용하여 WebSocket에 연결하고 STOMP 클라이언트를 생성합니다. 연결이 성공하면 특정 토픽('/topic/messages')을 구독하여 서버로부터 오는 메시지를 수신 대기합니다.

    • sendMessage(): 사용자가 입력한 메시지를 JSON 형식으로 만들어 STOMP 클라이언트를 통해 서버의 특정 경로('/app/sendMessage')로 발행(전송)합니다.

    • showMessage(): 구독 중인 토픽으로 메시지가 수신되면 이 함수가 호출되어 채팅창에 메시지를 표시합니다.

    • 주의: JavaScript 코드의 /ws-endpoint/topic/messages/app/sendMessage 와 같은 WebSocket 접속 및 메시지 송수신 경로는 실제 개발된 전자정부프레임워크 백엔드의 설정에 따라 정확하게 수정해야 합니다.

  5. 실행: 위 코드를 .html 파일로 저장하고 웹 브라우저에서 열면 UI를 확인할 수 있습니다. STOMP 기능은 백엔드 서버가 실행되고 경로가 올바르게 설정되었을 때 정상적으로 동작합니다.



<!DOCTYPE html>

<html lang="ko">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>웹 메신저</title>

    <!-- Bootstrap 4 CSS -->

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

    <!-- Font Awesome Icons -->

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">

    <style>

        body, html {

            height: 100%;

            margin: 0;

            font-family: 'Malgun Gothic', sans-serif;

            overflow: hidden;

        }

        .messenger-container {

            display: flex;

            flex-direction: column;

            height: 100vh;

            border: 1px solid #dee2e6;

            box-shadow: 0 0 10px rgba(0,0,0,0.1);

        }

        /* 최상단 바 */

        .top-bar {

            display: flex;

            justify-content: space-between;

            align-items: center;

            padding: 5px 10px;

            background-color: #f8f9fa;

            border-bottom: 1px solid #dee2e6;

            -webkit-app-region: drag; /* 창 드래그 가능 영역 */

        }

        .top-bar .logo {

            font-weight: bold;

            font-size: 1.1rem;

        }

        .top-bar .window-controls i {

            margin-left: 15px;

            cursor: pointer;

            -webkit-app-region: no-drag; /* 아이콘은 드래그 제외 */

        }

        /* 상단 메뉴 */

        .top-menu {

            display: flex;

            align-items: center;

            padding: 10px;

            background-color: #fff;

            border-bottom: 1px solid #dee2e6;

        }

        .top-menu .profile-img {

            width: 50px;

            height: 50px;

            border-radius: 50%;

            margin-right: 10px;

        }

        .top-menu .user-info {

            flex-grow: 1;

        }

        .top-menu .user-info .user-name {

            font-weight: bold;

        }

        .top-menu .user-info .user-status {

            font-size: 0.8rem;

            color: #6c757d;

        }

        .top-menu .user-info .user-status .status-dot {

            display: inline-block;

            width: 8px;

            height: 8px;

            background-color: #28a745; /* 온라인 상태 */

            border-radius: 50%;

            margin-right: 5px;

        }

        .top-menu .action-icons i {

            font-size: 1.2rem;

            margin-left: 20px;

            color: #6c757d;

            cursor: pointer;

        }

        /* 메인 컨텐츠 */

        .main-content {

            display: flex;

            flex-grow: 1;

            height: calc(100% - 122px); /* 상단바, 상단메뉴 높이 제외 */

        }

        /* 왼쪽 탭 메뉴 */

        .left-tabs {

            width: 80px;

            background-color: #f8f9fa;

            border-right: 1px solid #dee2e6;

            padding: 10px 0;

        }

        .left-tabs .nav-link {

            text-align: center;

            color: #6c757d;

            padding: 15px 0;

        }

        .left-tabs .nav-link.active {

            background-color: #e9ecef;

            color: #000;

        }

        .left-tabs .nav-link i {

            font-size: 1.8rem;

        }

        .left-tabs .nav-link span {

            display: block;

            font-size: 0.7rem;

            margin-top: 5px;

        }

        /* 탭 컨텐츠 (조직도, 채팅창 등) */

        .tab-content-area {

            flex-grow: 1;

            display: flex;

            flex-direction: column;

        }

        .tab-pane {

            height: 100%;

            overflow-y: auto;

        }

        /* 채팅창 */

        .chat-area {

            display: flex;

            flex-direction: column;

            height: 100%;

        }

        .chat-messages {

            flex-grow: 1;

            padding: 20px;

            overflow-y: auto;

            background-color: #e9ecef;

        }

        .message {

            margin-bottom: 15px;

            display: flex;

        }

        .message .msg-bubble {

            padding: 10px 15px;

            border-radius: 15px;

            max-width: 70%;

        }

        .message.sent {

            justify-content: flex-end;

        }

        .message.sent .msg-bubble {

            background-color: #007bff;

            color: white;

            border-bottom-right-radius: 3px;

        }

        .message.received {

            justify-content: flex-start;

        }

        .message.received .msg-bubble {

            background-color: #fff;

            border: 1px solid #dee2e6;

            border-bottom-left-radius: 3px;

        }

        .chat-input {

            padding: 10px;

            border-top: 1px solid #dee2e6;

            background-color: #fff;

        }

        .chat-input textarea {

            height: 80px;

            resize: none;

        }

        /* 조직도 */

        .org-chart { padding: 20px; }

        .org-chart ul {

            padding-left: 20px;

            list-style: none;

            position: relative;

        }

        .org-chart li {

            padding: 5px 0;

            position: relative;

        }

        .org-chart li::before, .org-chart li::after {

            content: '';

            position: absolute;

            left: -15px;

        }

        .org-chart li::before {

            border-top: 1px solid #ccc;

            top: 17px;

            width: 15px;

            height: 0;

        }

        .org-chart li::after {

            border-left: 1px solid #ccc;

            height: 100%;

            width: 0px;

            top: -9px;

        }

        .org-chart ul > li:last-child::after {

            height: 25px;

        }

        .org-chart a { color: #333; }

        .org-chart a:hover { text-decoration: none; color: #007bff; }

    </style>

</head>

<body>

    <div class="messenger-container">

        <!-- 최상단 바 -->

        <div class="top-bar">

            <div class="logo">회사 로고</div>

            <div class="window-controls">

                <i class="fas fa-cog"></i>

                <i class="far fa-window-minimize"></i>

                <i class="far fa-window-close"></i>

            </div>

        </div>


        <!-- 상단 메뉴 -->

        <div class="top-menu">

            <img src="https://via.placeholder.com/50" alt="Profile" class="profile-img">

            <div class="user-info">

                <div class="user-name">홍길동</div>

                <div class="user-status">

                    <span class="status-dot"></span> 온라인

                </div>

            </div>

            <div class="action-icons">

                <i class="fas fa-comment-plus"></i>

                <i class="fas fa-paperclip"></i>

            </div>

        </div>


        <!-- 메인 컨텐츠 -->

        <div class="main-content">

            <!-- 왼쪽 탭 메뉴 -->

            <div class="left-tabs">

                <ul class="nav nav-pills flex-column">

                    <li class="nav-item">

                        <a class="nav-link active" data-toggle="pill" href="#chat-main">

                            <i class="fas fa-comments"></i>

                            <span>대화</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#org-chart">

                            <i class="fas fa-sitemap"></i>

                            <span>조직도</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#notice">

                            <i class="fas fa-bullhorn"></i>

                            <span>공지</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#survey">

                            <i class="fas fa-poll"></i>

                            <span>설문</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#memo">

                            <i class="fas fa-sticky-note"></i>

                            <span>메모</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#link">

                            <i class="fas fa-link"></i>

                            <span>링크</span>

                        </a>

                    </li>

                    <li class="nav-item">

                        <a class="nav-link" data-toggle="pill" href="#sms">

                            <i class="fas fa-sms"></i>

                            <span>문자</span>

                        </a>

                    </li>

                </ul>

            </div>


            <!-- 탭 컨텐츠 영역 -->

            <div class="tab-content-area tab-content">

                <!-- 기본 채팅 탭 -->

                <div class="tab-pane fade show active" id="chat-main">

                    <div class="chat-area">

                        <div class="chat-messages" id="chat-messages">

                            <!-- 메시지 예시 -->

                            <div class="message received">

                                <div class="msg-bubble">안녕하세요. 메시지 테스트입니다.</div>

                            </div>

                            <div class="message sent">

                                <div class="msg-bubble">네, 안녕하세요. 잘 받았습니다.</div>

                            </div>

                        </div>

                        <div class="chat-input">

                            <form id="message-form">

                                <div class="form-group">

                                    <textarea id="message-input" class="form-control" placeholder="메시지를 입력하세요..."></textarea>

                                </div>

                                <button type="submit" class="btn btn-primary btn-block">전송</button>

                            </form>

                        </div>

                    </div>

                </div>


                <!-- 조직도 탭 -->

                <div class="tab-pane fade" id="org-chart">

                    <div class="org-chart">

                        <h5>조직도</h5>

                        <ul>

                            <li><a href="#"><strong>대표이사</strong></a>

                                <ul>

                                    <li><a href="#">경영지원본부</a>

                                        <ul>

                                            <li><a href="#">인사팀</a></li>

                                            <li><a href="#">총무팀</a></li>

                                        </ul>

                                    </li>

                                    <li><a href="#">개발본부</a>

                                        <ul>

                                            <li><a href="#">플랫폼개발팀</a></li>

                                            <li><a href="#">솔루션개발팀</a></li>

                                            <li><a href="#">모바일개발팀</a></li>

                                        </ul>

                                    </li>

                                     <li><a href="#">사업본부</a>

                                        <ul>

                                            <li><a href="#">사업1팀</a></li>

                                            <li><a href="#">사업2팀</a></li>

                                        </ul>

                                    </li>

                                </ul>

                            </li>

                        </ul>

                    </div>

                </div>


                <!-- 기타 탭 (공지, 설문 등) -->

                <div class="tab-pane fade" id="notice"><div class="p-3">공지사항 내용</div></div>

                <div class="tab-pane fade" id="survey"><div class="p-3">설문 내용</div></div>

                <div class="tab-pane fade" id="memo"><div class="p-3">메모 내용</div></div>

                <div class="tab-pane fade" id="link"><div class="p-3">링크 내용</div></div>

                <div class="tab-pane fade" id="sms"><div class="p-3">문자 내용</div></div>

            </div>

        </div>

    </div>


    <!-- jQuery, Popper.js, Bootstrap JS -->

    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

    

    <!-- SockJS and STOMP client -->

    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>

    <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>


    <script>

        $(document).ready(function() {

            var stompClient = null;

            var chatContainer = $('#chat-messages');


            // STOMP 연결

            function connect() {

                // 전자정부프레임워크의 WebSocket 설정 엔드포인트에 맞춰 수정해야 합니다.

                var socket = new SockJS('/ws-endpoint'); 

                stompClient = Stomp.over(socket);

                stompClient.connect({}, function(frame) {

                    console.log('Connected: ' + frame);

                    

                    // 메시지를 수신할 토픽을 구독합니다. (예: /topic/chat)

                    stompClient.subscribe('/topic/messages', function(message) {

                        showMessage(JSON.parse(message.body));

                    });

                }, function(error) {

                    console.log('STOMP connection error: ' + error);

                    // 연결 실패 시 재시도 로직 추가 가능

                });

            }


            // 연결 해제

            function disconnect() {

                if (stompClient !== null) {

                    stompClient.disconnect();

                }

                console.log("Disconnected");

            }

            

            // 메시지 전송

            function sendMessage() {

                var messageInput = $('#message-input');

                var messageContent = messageInput.val().trim();


                if (messageContent && stompClient) {

                    var chatMessage = {

                        sender: "홍길동", // 실제로는 로그인된 사용자 이름

                        content: messageContent

                    };

                    // 메시지를 발행할 엔드포인트 (예: /app/chat.sendMessage)

                    stompClient.send("/app/sendMessage", {}, JSON.stringify(chatMessage));

                    

                    // 보낸 메시지를 화면에 표시

                    appendMessage(chatMessage, 'sent');

                    messageInput.val('');

                }

            }

            

            // 받은 메시지를 화면에 표시하는 함수

            function showMessage(message) {

                // 받은 메시지를 화면에 표시 (자신이 보낸 메시지가 아닐 경우)

                if(message.sender !== "홍길동") { // 발신자 비교 로직 필요

                    appendMessage(message, 'received');

                }

            }


            // 메시지를 채팅창에 추가하는 함수

            function appendMessage(message, type) {

                var messageHtml = '<div class="message ' + type + '">' +

                                    '<div class="msg-bubble">' +

                                        '<div>' + message.content + '</div>' +

                                    '</div>' +

                                  '</div>';

                chatContainer.append(messageHtml);

                chatContainer.scrollTop(chatContainer[0].scrollHeight); // 스크롤을 맨 아래로

            }


            // 메시지 전송 폼 이벤트

            $('#message-form').on('submit', function(e) {

                e.preventDefault();

                sendMessage();

            });


            // 페이지 로드 시 STOMP 연결 시작

            // connect();

            // 주석 처리된 connect() 함수를 호출하여 실제 서버와 연결을 시작할 수 있습니다.

            

            // 초기 스크롤 맨 아래로

            chatContainer.scrollTop(chatContainer[0].scrollHeight);

        });

    </script>

</body>

</html>