first commit
This commit is contained in:
163
templates/layout/base.html
Normal file
163
templates/layout/base.html
Normal file
@@ -0,0 +1,163 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ .Title }}</title>
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- TradingView Lightweight Charts v4 (v5는 API 호환성 문제로 고정) -->
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.1/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/custom.css">
|
||||
<style>
|
||||
/* 상승/하락 색상 (한국 주식 관행) */
|
||||
.price-up { color: #ef4444; } /* 빨강 */
|
||||
.price-down { color: #3b82f6; } /* 파랑 */
|
||||
.price-flat { color: #6b7280; } /* 회색 */
|
||||
|
||||
/* 사이드바 토글 애니메이션 */
|
||||
#sidebarWrapper {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
transition: width 0.25s ease, opacity 0.25s ease, margin 0.25s ease;
|
||||
width: 240px;
|
||||
opacity: 1;
|
||||
}
|
||||
#sidebarWrapper.sidebar-hidden {
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
|
||||
<!-- 네비게이션 -->
|
||||
<nav class="bg-white shadow-sm sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-14">
|
||||
<!-- 사이드바 토글 버튼 -->
|
||||
<button id="sidebarToggle" title="사이드바 열기/닫기"
|
||||
class="mr-2 p-1.5 rounded-lg text-gray-500 hover:bg-gray-100 transition-colors shrink-0">
|
||||
<!-- 사이드바 열림: 왼쪽 화살표(닫기) -->
|
||||
<svg id="sidebarIconOpen" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
<!-- 사이드바 닫힘: 오른쪽 화살표(열기) -->
|
||||
<svg id="sidebarIconClose" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 로고 -->
|
||||
<a href="/" class="text-lg font-bold text-gray-800 hover:text-blue-600 shrink-0 mr-2">
|
||||
📈 주식 시세
|
||||
</a>
|
||||
<!-- 네비 메뉴 -->
|
||||
<nav class="flex items-center gap-0.5 shrink-0">
|
||||
<a href="/" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
|
||||
{{ if eq .ActiveMenu "시세" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">시세</a>
|
||||
<a href="/theme" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
|
||||
{{ if eq .ActiveMenu "테마" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">테마</a>
|
||||
<a href="/kospi200" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
|
||||
{{ if eq .ActiveMenu "코스피200" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">코스피200</a>
|
||||
{{ if .LoggedIn }}
|
||||
<a href="/asset" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
|
||||
{{ if eq .ActiveMenu "자산" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">자산</a>
|
||||
<a href="/autotrade" class="text-sm px-3 py-1.5 rounded-lg font-medium transition-colors
|
||||
{{ if eq .ActiveMenu "자동매매" }}bg-blue-50 text-blue-600{{ else }}text-gray-500 hover:bg-gray-100 hover:text-gray-800{{ end }}">자동매매</a>
|
||||
{{ end }}
|
||||
</nav>
|
||||
<!-- 검색창 -->
|
||||
<div class="flex-1 max-w-md mx-4 relative">
|
||||
<input
|
||||
id="searchInput"
|
||||
type="text"
|
||||
placeholder="종목명 또는 코드 검색"
|
||||
autocomplete="off"
|
||||
class="w-full px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||||
>
|
||||
<!-- 자동완성 드롭다운 -->
|
||||
<div id="searchDropdown" class="hidden absolute top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-60 overflow-y-auto"></div>
|
||||
</div>
|
||||
<!-- 로그인/로그아웃 버튼 -->
|
||||
{{ if .LoggedIn }}
|
||||
<form method="POST" action="/logout" class="shrink-0">
|
||||
<button type="submit"
|
||||
class="text-xs px-3 py-1.5 rounded-lg text-gray-500 hover:bg-gray-100 hover:text-gray-800 font-medium transition-colors">
|
||||
로그아웃
|
||||
</button>
|
||||
</form>
|
||||
{{ else }}
|
||||
<a href="/login" class="shrink-0 text-xs px-3 py-1.5 rounded-lg text-blue-600 hover:bg-blue-50 font-medium transition-colors">
|
||||
로그인
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 지수 티커 바 -->
|
||||
<div class="bg-gray-800 text-white text-xs">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-8 flex items-center gap-6 overflow-x-auto" id="indexTicker">
|
||||
<span class="text-gray-400 shrink-0">로딩 중...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 flex gap-6 items-start">
|
||||
<div id="sidebarWrapper">
|
||||
{{ block "sidebar" . }}{{ end }}
|
||||
</div>
|
||||
<main class="flex-1 min-w-0">
|
||||
{{ block "content" . }}{{ end }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 공통 JavaScript -->
|
||||
<script src="/static/js/websocket.js"></script>
|
||||
<script src="/static/js/search.js"></script>
|
||||
<script src="/static/js/indices.js"></script>
|
||||
{{ block "scripts" . }}{{ end }}
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const wrapper = document.getElementById('sidebarWrapper');
|
||||
const toggle = document.getElementById('sidebarToggle');
|
||||
const iconOpen = document.getElementById('sidebarIconOpen');
|
||||
const iconClose = document.getElementById('sidebarIconClose');
|
||||
if (!wrapper || !toggle) return;
|
||||
|
||||
// 사이드바가 없는 페이지(sidebar 블록이 비어 있음)는 버튼 숨김
|
||||
if (!wrapper.children.length) {
|
||||
toggle.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'sidebar_visible';
|
||||
|
||||
function applyState(visible) {
|
||||
if (visible) {
|
||||
wrapper.classList.remove('sidebar-hidden');
|
||||
iconOpen.classList.remove('hidden');
|
||||
iconClose.classList.add('hidden');
|
||||
} else {
|
||||
wrapper.classList.add('sidebar-hidden');
|
||||
iconOpen.classList.add('hidden');
|
||||
iconClose.classList.remove('hidden');
|
||||
}
|
||||
localStorage.setItem(STORAGE_KEY, visible ? '1' : '0');
|
||||
}
|
||||
|
||||
// 저장된 상태 복원 (기본값: 표시)
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
applyState(saved !== '0');
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
const isHidden = wrapper.classList.contains('sidebar-hidden');
|
||||
applyState(isHidden); // 현재 숨겨져 있으면 보이게
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user