mirror of
https://github.com/headporter81/specialsource-homepage-frontend.git
synced 2026-08-08 15:21:11 +09:00
Initial commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<h2>SpecialSource 로그인</h2>
|
||||
<form @submit.prevent="handleLogin">
|
||||
<input v-model="username" type="text" placeholder="아이디" required />
|
||||
<input v-model="password" type="password" placeholder="비밀번호" required />
|
||||
<button type="submit">로그인</button>
|
||||
</form>
|
||||
<p v-if="errorMsg" class="error">{{ errorMsg }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const errorMsg = ref('')
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
// Vite 프록시 설정을 통해 백엔드로 POST 요청을 날립니다.
|
||||
const response = await axios.post('/api/login', {
|
||||
username: username.value,
|
||||
password: password.value
|
||||
})
|
||||
|
||||
// 정상적으로 토큰을 받으면 브라우저에 저장하고 메인으로 이동!
|
||||
localStorage.setItem('token', response.data.token)
|
||||
router.push('/')
|
||||
} catch (error) {
|
||||
errorMsg.value = error.response?.data || '로그인에 실패했습니다.'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background: #0f172a; }
|
||||
.login-box { background: white; padding: 40px; border-radius: 8px; width: 350px; text-align: center; }
|
||||
input { width: 100%; padding: 12px; margin: 10px 0; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 4px; }
|
||||
button { width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
|
||||
button:hover { background: #2563eb; }
|
||||
.error { color: #ef4444; margin-top: 15px; font-size: 14px; }
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">👤 Special Source</div>
|
||||
<nav class="menu">
|
||||
<a href="#" class="active">🏠 홈/메인</a>
|
||||
<a href="#">📊 서비스 현황</a>
|
||||
<a href="#">⚙️ 시스템 설정</a>
|
||||
<button @click="handleLogout" class="btn-logout">🔓 로그아웃</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div class="main-container">
|
||||
<header class="navbar">
|
||||
<h2>Flexible UI Dashboard</h2>
|
||||
<div class="user-info">접속계정: <strong>admin</strong></div>
|
||||
</header>
|
||||
|
||||
<main class="content">
|
||||
<div class="card-grid">
|
||||
<div class="card status">🟢 NAS Server Running (Port: 18081)</div>
|
||||
<div class="card">
|
||||
<h3>Vite + Vue 3</h3>
|
||||
<p>초고속 HMR 빌드 엔진 탑재 완료</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>JWT Authentication</h3>
|
||||
<p>보안 통신 필터 및 CSRF 해제 상태</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('token')
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 📐 Flexbox & Grid 기반 플렉서블 레이아웃 */
|
||||
.app-layout { display: flex; height: 100vh; width: 100vw; overflow: hidden; font-family: sans-serif; }
|
||||
.sidebar { width: 240px; background-color: #1e293b; color: white; display: flex; flex-direction: column; padding: 20px; transition: all 0.3s; }
|
||||
.logo { font-size: 20px; font-weight: bold; margin-bottom: 30px; padding-bottom: 15px; border-bottom: 1px solid #334155; }
|
||||
.menu { display: flex; flex-direction: column; gap: 10px; height: 100%; }
|
||||
.menu a { color: #94a3b8; text-decoration: none; padding: 12px; border-radius: 6px; transition: 0.2s; }
|
||||
.menu a:hover, .menu a.active { color: white; background: #334155; }
|
||||
.btn-logout { margin-top: auto; padding: 12px; background: #ef4444; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; }
|
||||
|
||||
.main-container { flex: 1; display: flex; flex-direction: column; background-color: #f1f5f9; overflow: hidden; }
|
||||
.navbar { height: 60px; background: white; display: flex; justify-content: space-between; align-items: center; padding: 0 30px; border-bottom: 1px solid #e2e8f0; }
|
||||
.content { flex: 1; padding: 30px; overflow-y: auto; }
|
||||
|
||||
/* 반응형 카드 그리드 */
|
||||
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }
|
||||
.card { background: white; padding: 25px; border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); border: 1px solid #e2e8f0; }
|
||||
.card.status { grid-column: 1 / -1; background: #bbf7d0; color: #166534; font-weight: bold; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user