ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • vue.js 프로젝트: 일정 관리가 포함된 홈페이지 만들기
    정보교육/프런트엔드 2024. 6. 23. 22:05

    vue.js를 공부한 다음 인공지능 chatGPT 4o를 사용해서 만들어보았다. 역시 공부하기 전에는 기본 구조부터 프로그램의 돌아가는 원리를 잘 이해하지 못해 gpt를 사용해도 만들지 못했을 것이다. 공부한 후에는 구조와 원리를 파악한 후 수정할 부분을 파악할 수 있었고 익스플로어나 크롬에 있는 개발자 도구를 사용해 버그도 찾아낼 수 있었다. 

    우선 재사용 가능한 컴포넌트를 만드는데 집중하였고, 그 컴포넌트를 여기저기 쓸 수 있게 되었다. 스케쥴 등록, 수정, 삭제가능한 컴포넌트를 만들었고 그 다음 캘린더 컴포넌트를 붙이고 emit 명령어를 사용하여 연결하였다. 이 과정에서 개발자 도구를 사용하여 에러를 찾아서 수정하였다. 특별히 데이터베이스를 사용하지 않고 로컬 저장소를 사용하여 아주 간단한 로직을 구현하였다.

    Calendar.vue

    <template>
      <div class="calendar">
        <div class="header">
          <button @click="prevMonth">Prev</button>
          <h2>{{ monthNames[currentMonth] }} {{ currentYear }}</h2>
          <button @click="nextMonth">Next</button>
        </div>
        <div class="weekdays">
          <div v-for="day in weekDays" :key="day">{{ day }}</div>
        </div>
        <div class="days">
          <div
            v-for="day in daysInMonth"
            :key="day.date ? day.date.toDateString() : day.index"
            :class="{'today': isToday(day.date), 'has-event': hasEvent(day.date)}"
          >
            <span v-if="day.date">{{ day.date.getDate() }}</span>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        events: Array
      },
      data() {
        return {
          currentDate: new Date(),
          currentMonth: new Date().getMonth(),
          currentYear: new Date().getFullYear(),
          weekDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
          monthNames: [
            'January', 'February', 'March', 'April', 'May', 'June',
            'July', 'August', 'September', 'October', 'November', 'December'
          ]
        };
      },
      computed: {
        daysInMonth() {
          const days = [];
          const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
          const lastDate = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
          for (let i = 0; i < firstDay; i++) {
            days.push({ date: null, index: `empty-${i}` });
          }
          for (let date = 1; date <= lastDate; date++) {
            days.push({ date: new Date(this.currentYear, this.currentMonth, date) });
          }
          return days;
        }
      },
      methods: {
        prevMonth() {
          if (this.currentMonth === 0) {
            this.currentMonth = 11;
            this.currentYear--;
          } else {
            this.currentMonth--;
          }
        },
        nextMonth() {
          if (this.currentMonth === 11) {
            this.currentMonth = 0;
            this.currentYear++;
          } else {
            this.currentMonth++;
          }
        },
        isToday(date) {
          if (!date) return false;
          const today = new Date();
          return date.getDate() === today.getDate() &&
                 date.getMonth() === today.getMonth() &&
                 date.getFullYear() === today.getFullYear();
        },
        hasEvent(date) {
          if (!date) return false;
          return this.events.some(event => {
            const eventDate = new Date(event.date);
            return eventDate.getDate() === date.getDate() &&
                   eventDate.getMonth() === date.getMonth() &&
                   eventDate.getFullYear() === date.getFullYear();
          });
        }
      }
    };
    </script>
    
    <style scoped>
    .calendar {
      max-width: 400px;
      margin: auto;
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
    .header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .weekdays {
      display: flex;
      justify-content: space-between;
      margin-top: 10px;
    }
    .days {
      display: flex;
      flex-wrap: wrap;
    }
    .days div {
      width: calc(100% / 7);
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      margin-top: 10px;
    }
    .today {
      background-color: #42b983;
      color: white;
      border-radius: 50%;
    }
    .has-event {
      background-color: purple;
      color: white;
      border-radius: 50%;
    }
    </style>

    Schedule.vue

    <template>
        <div class="schedule">
          <h2>My Schedule</h2>
          <ul>
            <li v-for="event in events" :key="event.id">
              <span @click="editEvent(event)">{{ event.date }} {{ event.time }}: {{ event.title }}</span>
              <button @click="deleteEvent(event.id)">Delete</button>
            </li>
          </ul>
          <input v-model="newEventTitle" placeholder="Event Title" />
          <input v-model="newEventDate" type="date" />
          <input v-model="newEventTime" type="time" />
          <button @click="addEvent">Add Event</button>
          <button v-if="isEditing" @click="updateEvent">Update Event</button>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {
            events: JSON.parse(localStorage.getItem('events') || '[]'),
            newEventTitle: '',
            newEventDate: '',
            newEventTime: '',
            isEditing: false,
            currentEvent: null
          };
        },
        methods: {
          addEvent() {
            if (this.newEventTitle && this.newEventDate && this.newEventTime) {
              const newEvent = {
                id: Date.now(),
                title: this.newEventTitle,
                date: this.newEventDate,
                time: this.newEventTime
              };
              this.events.push(newEvent);
              this.saveEvents();
              this.resetForm();
              this.$emit('update-events', this.events);
            }
          },
          editEvent(event) {
            this.isEditing = true;
            this.currentEvent = event;
            this.newEventTitle = event.title;
            this.newEventDate = event.date;
            this.newEventTime = event.time;
          },
          updateEvent() {
            if (this.currentEvent) {
              this.currentEvent.title = this.newEventTitle;
              this.currentEvent.date = this.newEventDate;
              this.currentEvent.time = this.newEventTime;
              this.saveEvents();
              this.resetForm();
              this.$emit('update-events', this.events);
            }
          },
          deleteEvent(id) {
            this.events = this.events.filter(event => event.id !== id);
            this.saveEvents();
            this.$emit('update-events', this.events);
          },
          saveEvents() {
            localStorage.setItem('events', JSON.stringify(this.events));
          },
          resetForm() {
            this.newEventTitle = '';
            this.newEventDate = '';
            this.newEventTime = '';
            this.isEditing = false;
            this.currentEvent = null;
          }
        }
      };
      </script>
      
      
      <style scoped>
      .schedule {
        max-width: 400px;
        margin: auto;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 8px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      }
      input {
        display: block;
        width: 100%;
        padding: 8px;
        margin: 10px 0;
        border: 1px solid #ccc;
        border-radius: 4px;
      }
      button {
        padding: 10px 15px;
        background-color: #42b983;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
      }
      button:hover {
        background-color: #38a373;
      }
      ul {
        list-style-type: none;
        padding: 0;
      }
      li {
        margin: 10px 0;
      }
      span {
        cursor: pointer;
      }
      </style>

    마지막으로 홈페이지 구실을 하기위해 header, menu, article, side, footer를 구성하고 side에 캘린더와 일정 관리를 재사용하였다. 

    SideBar.vue

    <template>
        <div>
          <Calendar :events="events" />
          <Schedule @update-events="updateEvents" />
        </div>
      </template>
      
      <script>
      import Calendar from './Calendar.vue';
      import Schedule from './Schedule.vue';
      
      export default {
        name: 'SideBar',
        components: {
          Calendar,
          Schedule
        },
        data() {
          return {
            events: JSON.parse(localStorage.getItem('events') || '[]')
          };
        },
        methods: {
          updateEvents(events) {
            this.events = events;
          }
        }
      };
      </script>

    결과물과 만드는 과정은 아래 링크를 통해서 확인할 수 있다. 단 이 페이지는 최종 완성물이 아니라서 최적화 과정을 거쳐서 자신만의 웹페이지를 만들 수 있으면 한다. 

    My Personal Website (jocular-boba-1b5d4b.netlify.app)

     

    My Personal Website

     

    jocular-boba-1b5d4b.netlify.app

     

Designed by Tistory.