hola 개발

[ Vue ] 영화 검색 사이트 본문

프로젝트

[ Vue ] 영화 검색 사이트

hola. 2025. 5. 8. 17:54

- assets 

-- movie.js

let data = [
    {
      title: "노랑",
      year: 2023,
      category: "액션, 드라마마",
      textRed: "color: red",
      like: 0,
      imgUrl: './assets/logo.png'
    },
    {
      title: "아쿠아맨과 로스트 킹덤",
      year: 2023,
      category: "액션, 드라마마",
      like: 0,
      imgUrl: './assets/루미너스메모리0009.jpg'
    },
]

export default data;

 

 

App.vue

<template>
  <Navbar />
  <Event :text="text" />
  <Searchbar :data="data_temp" @searchMovie="searchMovie($event)"/>
  <Movies :data="data_temp" @openModal="isModal=true;selectedMovie=$event" @increaseLike="increaseLike($event)"/>
  <Modal :data="data" :isModal="isModal" :selectedMovie="selectedMovie" @closeModal="isModal=false"/>
</template>

<script>
  import data from './assets/movie.js';
  import Navbar from './components/Navbar.vue';
  import Modal from './components/Modal.vue';
  import Event from './components/Event.vue';
  import Movies from './components/Movies.vue';
  import Searchbar from './components/Searchbar.vue';

  export default{
    name: 'App',
    data(){
      return {
        isModal: false,
        data: data, // 원본
        //원래있던 데이터 복사
        data_temp: [...data], // 사본본
        selectedMovie: 0,
        text: "NEPLIX 경기 크러처!!",
      }
    },
    methods: {
      increaseLike(i){
        this.data[i].like ++;
      },
      searchMovie(title){
        // 영화제목이 포함된 데이터를 가져옴
        this.data_temp = this.data.filter(movie => {
          return movie.title.includes(title);
        })
      }
    },
    components: {
      Navbar: Navbar,
      Event: Event,
      Modal: Modal,
      Movies: Movies,
      Searchbar: Searchbar,
    }
  }


</script>

<style>
  * {
    box-sizing: border-box;
    margin: 0;
  }

  body {
    max-width: 768px;
    margin: 0 auto;
    padding: 20px;
  }

  h1, h2, h3 {
    margin-bottom: 1rem;
  }

  p {
    margin-bottom: 0.5rem;
  }

  button {
    margin-right: 10px;
    margin-top: 1rem;
  }

  .item {
    width: 100%;
    border: 1px solid #ccc;
    display: flex;
    margin-bottom: 20px;
    padding: 1rem;
  }

  .item figure{
    width: 30%;
    margin-right: 1rem;
  }

  .item img {
    width: 100%;
  }

  .item .info {
    width: 100%;
  }

</style>

 

-components 

-- Event.vue

<template>
    <div class="event" :class="{show : isOpen}">
        <p>{{text}}</p>
        <button @click="isOpen=false">x</button>
    </div>
</template>
   
<script>
    export default {
        name: 'EventComponent',
        props: {
            text: String,
        },
        data(){
            return {
                isOpen: true,
            }
        }
    }
</script>
   
<style>
    .event {
        background-color: forestgreen;
        padding: 10px 20px;
        color: #fff;
        text-align: center;
        font-size: small;
        display: flex;
        justify-content: space-between;
        align-items: center;
        display: none;
    }
    .event p {
        margin: 0;
    }
    .show {
        display: flex;
    }

    .event button {
        margin: 0;
    }
</style>

 

-- Modal.vue

<template>
  <div class="modal" v-if="isModal">
    <div class="inner">
      <h3>{{data[selectedMovie].title}}</h3>
      <p>영화 상세정보</p>
      <button @click="$emit('closeModal')">닫기</button>
    </div>
  </div>      
</template>
   
<script>
    export default {
        name: 'ModalComponent',
        props: {
            data: Array,
            isModal: Boolean,
            selectedMovie: Number,
        }
    }    
</script>
   
<style>
  .modal {
    background:  rgba(0,0,0,0.7);
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .modal .inner {
    background: #fff;
    width: 80%;
    padding: 20px;
    border-radius: 10px;
  }    
</style>

 

-- Movies.vue

<template>
  <div class="container">
    <h1>영화 정보</h1>
    <div v-for="(movie, i) in data" :key="i" class="item">
        <figure>
          <img :src="`${movie.imgUrl}`" :alt="movie.title">
        </figure>

        <div class="info">
        <h3 class="bg-yellow" :style="textRed" >{{movie.title}}</h3>
        <p>개봉: {{movie.year}}</p>
        <p>장르: {{movie.category}}</p>
        <button @click="$emit('increaseLike', i)">좋아요</button>
        <span>{{movie.like}}</span>
        <p>
            <button @click="$emit('openModal', i)">상세보기</button>
        </p>
        </div>
    </div>        
  </div>  
</template>
   
<script>
   export default{
        name: 'MoviesComponent',
        props: {
            data: Array,
        }
   }
</script>
   
<style>
    .container {
        padding: 20px;
    }
</style>

 

-- Navbar.vue

<template>
    <nav class="navbar">
        <a href="#">Home</a>
        <a href="#">Movies</a>
        <a href="#">About</a>
    </nav>
</template>
   
<script>
    export default {
        name: 'NavbarComponent',
    }
</script>
   
<style>
 .navbar {
    background: #000;
    padding: 20px;
    text-align: center;
 }

 .navbar a {
    color: #fff;
    text-decoration: none;
    padding: 1em;
 }
   
</style>

 

-- Searchbar.vue

<template>
    <div class="search-box">
        <input
            type="search"
            @change="
                $emit('searchMovie', $event.target.value)
                inputText=$event.target.value;
                $event.target.value = ''
            "
            placeholder="검색어 입력"
        >
        <button>검색</button>
    </div>

</template>
   
<script>
    export default {
        name: "SearchbarComponent",
        data() {
            return {
                inputText: '',
            }
        },
       
        props: {
            data: Array,
        },

        watch: {
            inputText(name) {
                // 입력한 영화제목이 데이터에 있는지 확인
                // data는 부모 -> 자식으로 props 로부 받은 data를 의미함
                // this를 쓰는 이유는 props로 받은 값이라도 접근할 때는 this.propName으로 해야하기 때문
                const findName = this.data.filter(movie => {
                    return movie.title.includes(name);
                })
                console.log(findName);
                if(findName.length == 0){
                    alert("해당하는 영화는 없습니다.");
                }
            }
        }
    }
</script>
   
<style>
    .search-box {
        padding: 10px;
        display: flex;
        justify-content: center;
    }

    .search-box input {
        padding: 5px 10px;
    }

    .search-box button {
        margin: 0;
    }
   
</style>

 

출처
https://www.youtube.com/watch?v=m2j_Y245xew&t=254s