주요 기능 및 특징
화면 구성: 최상단 로고 및 창 제어, 상단 메뉴(프로필, 대화, 상태), 좌측 탭 메뉴(조직도, 공지 등), 중앙 대화창으로 구성된 직관적인 UI 실시간 메시징: stomp.js 라이브러리를 활용하여 WebSocket을 통해 서버와 실시간으로 메시지를 주고받는 기능 구현 동적 UI: jQuery를 사용하여 탭 메뉴 전환, 메시지 전송 및 수신 시 동적으로 화면을 갱신 반응형 디자인: Bootstrap 4.x를 기반으로 하여 다양한 화면 크기에 대응 가능 아이콘: Font Awesome 아이콘을 사용하여 메뉴 및 버튼의 시인성 향상
데모 화면
프론트엔드 코드 (HTML, CSS, JavaScript)
코드 설명 및 사용법
라이브러리 포함: Bootstrap 4, Font Awesome, jQuery, SockJS, Stomp.js 라이브러리를 CDN을 통해 포함시켰습니다. HTML 구조: 요청사항에 맞게 top-bar, top-menu, main-content로 구조를 나누고, main-content 내부에 left-tabs와 tab-content-area를 배치했습니다. CSS 스타일: 각 영역의 디자인과 조직도(ul, li 태그 활용) 스타일을 정의했습니다. JavaScript (STOMP 연동): connect(): SockJS를 사용하여 WebSocket에 연결하고 STOMP 클라이언트를 생성합니다. 연결이 성공하면 특정 토픽('/topic/messages')을 구독하여 서버로부터 오는 메시지를 수신 대기합니다. sendMessage(): 사용자가 입력한 메시지를 JSON 형식으로 만들어 STOMP 클라이언트를 통해 서버의 특정 경로('/app/sendMessage')로 발행(전송)합니다. showMessage(): 구독 중인 토픽으로 메시지가 수신되면 이 함수가 호출되어 채팅창에 메시지를 표시합니다. 주의: JavaScript 코드의 /ws-endpoint, /topic/messages, /app/sendMessage 와 같은 WebSocket 접속 및 메시지 송수신 경로는 실제 개발된 전자정부프레임워크 백엔드의 설정에 따라 정확하게 수정해야 합니다.
실행: 위 코드를 .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>