Syw.Frontend

๐Ÿงฉ Vue 2๋กœ ๋ฐฐ์šฐ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ์ดˆ

2๋‹จ๊ณ„. ๋„ฅ์ŠคํŠธ๊ณต๋ฌด์› ๋ฉ”์ธ Vue + Express๋กœ ๋กœ ํด๋ก ํ•˜๊ธฐ

2-2. ๋ฉ”์ธ ๋ ˆ์ด์•„์›ƒ ์žก๊ธฐ - Header, Footer, MainSlide ๊ตฌํ˜„

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • ๋ฉ”๊ฐ€๊ณต๋ฌด์› ๋ฉ”์ธ ๊ตฌ์กฐ์˜ ์ „์ฒด ๋ ˆ์ด์•„์›ƒ์„ Vue ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ํผ๋ธ”๋ฆฌ์‹ฑํ•œ๋‹ค.
  • ํ™”๋ฉด ์ƒ๋‹จ๋ถ€ํ„ฐ ํ•˜๋‹จ๊นŒ์ง€์˜ ๊ณ ์ •/์œ ๋™ ๋ ˆ์ด์•„์›ƒ๊ณผ ์ขŒ์šฐ ์‚ฌ์ด๋“œ, ์Šฌ๋ผ์ด๋“œ ๊ตฌ์กฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

๐Ÿงฑ ์‹ค์Šต ๊ฐœ์š”

์ด๋ฒˆ ๊ฐ•์˜์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ „์ฒด ๋ ˆ์ด์•„์›ƒ์„ ๊ตฌ์„ฑํ•˜๊ณ  ์‹ค์ œ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

  • Header: ์ƒ๋‹จ ๊ณ ์ • ๋ฉ”๋‰ด
  • LeftQuickMenu, RightQuickMenu: ์ขŒ์šฐ ์‚ฌ์ด๋“œ ๊ณ ์ • ๋ฉ”๋‰ด
  • MainSlide: ์ขŒ์ธก ์ฝ˜ํ…์ธ  ์Šฌ๋ผ์ด๋“œ
  • MainTabs: ํƒญ + ์Šฌ๋ผ์ด๋“œ ์„น์…˜
  • StickyPanel: ์šฐ์ธก ์ฝ˜ํ…์ธ  ๊ณ ์ •
  • HomePage.vue: ์ „์ฒด ํŽ˜์ด์ง€ ๋ ˆ์ด์•„์›ƒ ํ†ตํ•ฉ

๐Ÿ›  ์‹ค์Šต ์ฝ”๋“œ: HomePage.vue ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ

<template>
  <div class="home-page">
    <div class="layout-container">
      <LeftQuickMenu />
      <main class="main-content">
        <section class="main-left">
          <MainSlide />
          <MainTabs />
        </section>
        <aside class="main-right">
          <StickyPanel />
        </aside>
      </main>
      <RightQuickMenu />
    </div>
  </div>
</template>

<script>
import LeftQuickMenu from "@/components/LeftQuickMenu.vue";
import RightQuickMenu from "@/components/RightQuickMenu.vue";
import MainSlide from "@/components/MainSlide.vue";
import MainTabs from "@/components/MainTabs.vue";
import StickyPanel from "@/components/StickyPanel.vue";

export default {
  components: {
    LeftQuickMenu,
    RightQuickMenu,
    MainSlide,
    MainTabs,
    StickyPanel,
  },
};
</script>

<style lang="scss" scoped>
.layout-container {
  display: flex;
  position: relative;
}

.main-content {
  display: flex;
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 20px;
}

.main-left {
  flex: 3;
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.main-right {
  flex: 1;
  position: sticky;
  top: 100px;
}
</style>

๐Ÿงฉ src/assets/base.scss

๋ฉ”๊ฐ€๊ณต๋ฌด์›์˜ ๋ฒ ์ด์Šค scss๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap");

:root {
  --font-noto: "Noto Sans KR";
  --font-gothic: "๋ง‘์€ ๊ณ ๋”•", "Malgun Gothic", "๋‹์›€", "Dotum", sans-serif;

  --gong-menuIcn-color1: #2642d1;
  --gong-menuIcn-color2: #0082ff;
  --sobang-menuIcn-color1: #b95253;
  --sobang-menuIcn-color2: #ff4141;
  --army-menuIcn-color1: #7a8e53;
}

/* reset */
html,
body {
  font-family: var(--font-gothic);
  font-size: 13px;
  color: #000;
  letter-spacing: -0.05em;
  line-height: 1.3;
  margin: 0;
  padding: 0;
  border: 0;
}
div,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
sub,
sup,
tt,
var,
b,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
  margin: 0;
  padding: 0;
  border: 0;
  color: inherit;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}
body {
  position: relative;
  line-height: 1;
}
ol,
ul {
  list-style: none;
}
blockquote,
q {
  quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
  content: "";
  content: none;
}
table {
  border-collapse: collapse;
  border: 0;
  table-layout: fixed; /*border-spacing:0px; zoom:1; empty-cells:show; width:100%; */
}
th,
td {
  word-wrap: break-word;
  line-height: 1.3;
  padding-left: 3px;
  padding-right: 3px;
}
img {
  vertical-align: top;
  max-width: 100%;
}
input[type="text"],
input[type="password"],
input[type="number"] {
  max-width: 100%;
  font-family: var(--font-gothic);
}
input[type="checkbox"],
input[type="radio"] {
  vertical-align: middle;
  position: relative;
  margin: 0;
  padding: 0;
}
textarea {
  font-family: var(--font-gothic) !important;
  font-size: 13px;
  color: #000;
}
#accessibility,
.skip,
legend,
caption {
  position: absolute;
  clip: rect(0 0 0 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
}
button {
  padding: 0;
  margin: 0;
  border: 0;
  background: 0;
  line-height: normal;
  cursor: pointer;
  font-family: var(--font-gothic);
}
button:focus {
  outline: none;
}
a {
  text-decoration: none;
  cursor: pointer;
}
a:link,
a:hover,
a:active {
  text-decoration: none;
  outline: none !important;
  color: inherit;
}
.clearfix {
  /**zoom:1*/
}
.f_left {
  float: left;
}
.f_right {
  float: right;
}
.d_flex {
  display: flex;
}
.clearfix:after {
  display: block;
  content: "";
  clear: both;
}
.ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;
  max-width: 90%;
}
.ellipsis2 {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
*:focus {
  outline: none;
}
del {
  text-decoration: line-through;
  color: #aeaeae;
}
.blindw {
  position: absolute;
  clip: rect(0 0 0 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
}

@-webkit-keyframes skeleton-gradient {
  0% {
    background-color: rgba(165, 165, 165, 0.1);
  }
  50% {
    background-color: rgba(165, 165, 165, 0.3);
  }
  100% {
    background-color: rgba(165, 165, 165, 0.1);
  }
}

@keyframes skeleton-line {
  100% {
    background-position: -100% 0;
  }
}

/*layout*/
html {
  margin: 0 auto;
}
#mWrap {
  position: relative;
}
.darkmask {
  position: absolute;
  z-index: 9998;
  background: #000;
  left: 0;
  top: 0;
}

/* Noto Sans KR Settings */

.mt10 {
  margin-top: 10px;
}
.mb0 {
  margin-bottom: 0px !important;
}
.mb30 {
  margin-bottom: 30px;
}

/* responsive */
/* [class*="show-"] { display: none !important; }  */
.show-1200,
.show-1024,
.show-992,
.show-768,
.show-640,
.show-576,
.show-460 {
  display: none !important;
}

@media all and (max-width: 1200px) {
  /* responsive */
  .hide-1200,
  br.hide-1200 {
    display: none !important;
  }
  .show-1200 {
    display: inherit !important;
  }
  .show-1200.block,
  br.show-1200 {
    display: block !important;
  }
  .show-1200.flex {
    display: flex !important;
  }
}

@media screen and (max-width: 1024px) {
  /* responsive */
  .show-1024 {
    display: inherit !important;
  }
  .show-1024.block,
  br.show-1024 {
    display: block !important;
  }
  .show-1024.flex {
    display: flex !important;
  }
  .hide-1024,
  br.hide-1024 {
    display: none !important;
  }
}

@media screen and (max-width: 992px) {
  /* responsive */
  .show-992 {
    display: inherit !important;
  }
  .show-992.block,
  br.show-992 {
    display: block !important;
  }
  .show-992.flex {
    display: flex !important;
  }
  .hide-992,
  br.hide-992 {
    display: none !important;
  }
}

@media screen and (max-width: 768px) {
  /* responsive */
  .show-768 {
    display: inherit !important;
  }
  .show-768.block,
  br.show-768 {
    display: block !important;
  }
  .show-768.flex {
    display: flex !important;
  }
  .hide-768,
  br.hide-768 {
    display: none !important;
  }
}

@media screen and (max-width: 640px) {
  /* responsive */
  .show-640 {
    display: inherit !important;
  }
  .show-640.block,
  br.show-640 {
    display: block !important;
  }
  .show-640.flex {
    display: flex !important;
  }
  .hide-640,
  br.hide-640 {
    display: none !important;
  }
}

@media all and (max-width: 576px) {
  /* responsive */
  .show-576 {
    display: inherit !important;
  }
  .show-576.block,
  br.show-576 {
    display: block !important;
  }
  .show-576.flex {
    display: flex !important;
  }
  .hide-576,
  br.hide-576 {
    display: none !important;
  }
}

@media all and (max-width: 460px) {
  /* responsive */
  .show-460 {
    display: inherit !important;
  }
  .show-460.block,
  br.show-460 {
    display: block !important;
  }
  .show-460.flex {
    display: flex !important;
  }
  .hide-460,
  br.hide-460 {
    display: none !important;
  }
}

๐Ÿงฉ src/components/MainSlide.vue

<template>
  <div class="main-slide">
    <div class="slide-banner">[ ์—ฌ๊ธฐ์— ์Šฌ๋ผ์ด๋“œ ๋ฐฐ๋„ˆ๊ฐ€ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค ]</div>
  </div>
</template>

<style scoped>
.main-slide {
  background: #eaf3ff;
  height: 300px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
</style>

๐Ÿงฉ src/components/Header.vue

<template>
  <header class="header">
    <div class="top-bar">
      <div class="top-inner">
        <div class="hcode_wrap">
          <router-link to="/" class="on">๊ณต๋ฌด์›</router-link>
          <router-link to="/sobang">์†Œ๋ฐฉ</router-link>
        </div>
        <div class="login_wrap">
          <div class="util">
            <span class="day">D-100</span>
            <router-link to="/academy" class="link">ํ•™์› ์ „์ฒด๋ณด๊ธฐ</router-link>
            <router-link to="/mypage/playground" class="link">ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ</router-link>
            <router-link to="/help/faq/" class="link">ํ•™์Šต์ง€์›์„ผํ„ฐ</router-link>
            <router-link to="/my_study" class="link">๋‚ด ๊ฐ•์˜์‹ค</router-link>
            <router-link to="/mypage" class="link">MY</router-link>
          </div>
          <div class="auth">
            <router-link to="/login" class="login">๋กœ๊ทธ์ธ</router-link>
            <router-link to="/register" class="join">ํšŒ์›๊ฐ€์ž…</router-link>
          </div>
        </div>
      </div>
    </div>

    <div class="gnb-area">
      <div class="gnb">
        <h1 class="logo">
          <router-link to="/">
            <img src="https://img.megagong.net/s/gong/logo_nextgong.png" alt="๋„ฅ์ŠคํŠธ๊ณต๋ฌด์›" />
          </router-link>
        </h1>
        <nav class="nav">
          <ul>
            <li><router-link to="/pass">๋„ฅ์ŠคํŠธํŒจ์Šค</router-link></li>
            <li><router-link to="/vod/vod_chr_list">์ˆ˜๊ฐ•์‹ ์ฒญ</router-link></li>
            <li><router-link to="/teacher">์„ ์ƒ๋‹˜</router-link></li>
            <li><router-link to="/fullservice">ํ•ฉ๊ฒฉ์˜ˆ์ธก ํ’€์„œ๋น„์Šค</router-link></li>
            <li><router-link to="/lab">์ˆ˜ํ—˜์ •๋ณด</router-link></li>
            <li><router-link to="/event">์ด๋ฒคํŠธ</router-link></li>
            <li><router-link to="/book">๊ต์žฌ</router-link></li>
          </ul>
        </nav>
      </div>
    </div>
  </header>
</template>

<script>
export default {
  name: "AppHeader",
};
</script>

<style lang="scss" scoped>
.header {
  border-bottom: 1px solid #ddd;
  background-color: white;

  .top-inner {
    width: 1200px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
  }

  .hcode_wrap {
    display: flex;
    gap: 10px;
    a.on {
      color: #0055ff;
    }
  }

  .login_wrap {
    display: flex;
  }

  .top-bar {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    padding: 10px 20px;
    background: #f8f8f8;
    color: #555;

    .util {
      display: flex;
      gap: 12px;

      .day {
        color: #0055ff;
        font-weight: bold;
      }

      .link {
        color: #333;
        text-decoration: none;
      }
    }

    .auth {
      display: flex;
      gap: 12px;

      a {
        color: #333;
        text-decoration: none;
      }
    }
  }
  .gnb-area {
    max-width: 1200px;
    margin: 0 auto;
    .gnb {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 30px 0px;

      .logo img {
        height: 28px;
      }

      .nav ul {
        display: flex;
        gap: 24px;

        li a {
          text-decoration: none;
          color: #111;
          font-weight: 500;
          font-size: 15px;

          &:hover {
            color: #0055ff;
          }
        }
      }
    }
  }
}
</style>

๐Ÿงฉ src/components/Footer.vue

<template>
  <footer class="footer">
    <div class="footer-inner">
      <ul class="footer-links">
        <li><a href="#">ํšŒ์‚ฌ์†Œ๊ฐœ</a></li>
        <li><a href="#">์ด์šฉ์•ฝ๊ด€</a></li>
        <li><a href="#">๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ๋ฐฉ์นจ</a></li>
        <li><a href="#">๊ณ ๊ฐ์„ผํ„ฐ</a></li>
      </ul>
      <div class="footer-info">
        <p>
          ์„œ์šธํŠน๋ณ„์‹œ ๊ตฌ๋กœ๊ตฌ ๊ฒฝ์ธ๋กœ 662, ํƒ€์›Œ๋™ 31์ธต(์‹ ๋„๋ฆผ๋™, ๋””ํ๋ธŒ์‹œํ‹ฐ) ใˆœ๋„ฅ์ŠคํŠธ์Šคํ„ฐ๋”” ๋Œ€ํ‘œ์ด์‚ฌ
          ์œคํ›ˆํฌ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์ฑ…์ž„์ž ์œคํ›ˆํฌ (keeper@megagong.net)
        </p>
        <p>
          ๋ฉ”๊ฐ€์Šคํ„ฐ๋””๊ต์œก ์›๊ฒฉํ‰์ƒ๊ต์œก์‹œ์„ค(์ œ434ํ˜ธ) (์ฃผ)๋„ฅ์ŠคํŠธ์Šคํ„ฐ๋”” ์‚ฌ์—…์ž๋“ฑ๋ก๋ฒˆํ˜ธ : 561-81-03379
          ํ†ต์‹ ํŒ๋งค์—…์‹ ๊ณ ๋ฒˆํ˜ธ : ์ œ2025-์„œ์šธ๊ตฌ๋กœ-1079ํ˜ธ
        </p>
        <p>์‹ ๊ณ ๊ธฐ๊ด€๋ช… : ์„œ์šธ์‹œ ๊ฐ•๋‚จ๊ตฌ ํ˜ธ์ŠคํŒ…์ œ๊ณต์ž : (์ฃผ)์ผ€์ดํ‹ฐ</p>
        <p class="copyright">Copyright ยฉ nextstudy.co.,Ltd. All rights reserved.</p>
      </div>
    </div>
  </footer>
</template>

<script>
export default {
  name: "AppFooter",
};
</script>

<style scoped lang="scss">
.footer {
  background-color: #f5f6f7;
  border-top: 1px solid #ddd;
  font-size: 13px;
  color: #666;

  .footer-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 24px 0;
  }

  .footer-links {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 12px;

    li {
      list-style: none;

      a {
        color: #444;
        text-decoration: none;

        &:hover {
          text-decoration: underline;
        }
      }
    }
  }

  .footer-info {
    line-height: 1.6;
  }

  .copyright {
    margin-top: 8px;
    font-size: 12px;
    color: #999;
  }
}
</style>

๐Ÿงฉ src/components/LeftQuickMenu.vue

<template>
  <div class="left-quickmenu">
    <div>[ ์ขŒ์ธก ์‚ฌ์ด๋“œ ๊ณ ์ • ๋ฉ”๋‰ด ]</div>
  </div>
</template>

๐Ÿงฉ src/components/RightQuickMenu.vue

<template>
  <div class="right-quickmenu">
    <div>[ ์šฐ์ธก ์‚ฌ์ด๋“œ ๊ณ ์ • ๋ฉ”๋‰ด ]</div>
  </div>
</template>

๐Ÿงฉ src/components/StickyPanel.vue

<template>
  <div class="sticky-panel">
    <div>[ ์šฐ์ธก ์ฝ˜ํ…์ธ  ๊ณ ์ • ]</div>
  </div>
</template>

๐Ÿงฉ src/components/MainTabs.vue

<template>
  <div class="main-tabs">
    <div class="slide-banner">[ ํƒญ + ์Šฌ๋ผ์ด๋“œ ์„น์…˜ ]</div>
  </div>
</template>

๐Ÿงฉ src/App.vue

<template>
  <div>
    <MainHeader />
    <router-view />
    <MainFooter />
  </div>
</template>

<script>
import MainHeader from "@/components/Header.vue";
import MainFooter from "@/components/Footer.vue";

export default {
  components: {
    MainHeader,
    MainFooter,
  },
};
</script>

์ •์ƒ์ ์œผ๋กœ ์ž˜ ๋”ฐ๋ผ์™”๋‹ค๋ฉด ์œ„์™€ ๊ฐ™์€ ํ™”๋ฉด์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์ €์žฅ์†Œ

GitHub - heroyooi/vue2-megagong at ch2

๐Ÿ“˜ ๊ณผ์ œ

  1. ์ƒ๋‹จ ๋ฉ”์ธ ์Šฌ๋ผ์ด๋“œ (MainVisual.vue) ์™„์„ฑ
    1. Slick or Swiper ๋กœ ๊ตฌํ˜„

๐Ÿ’ฌ ๋Œ“๊ธ€

    โ€ป ๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.