Vue로 나만의 캘린더 만들어보기 - 1
2020-09-03 23:14:41

Project 생성

책에서 보고 설치 했었던 CLI가 현재 버전보다 꽤 많이 낮아서 새로운 버전으로 교체를 하였다.

새 버전은 vue create로 프로젝트를 만들 수 있었는데 아직 이전 버전도 안 써봤으므로

이전 버전에서 제공하는 걸로 진행을 했다.

1
2
3
4
npm uninstall vue-cli -g
npm i @vue/cli -g
npm i @vue/cli-init # 이전 2.x 템플릿을 가져오기 위함
vue init webpack my_calendar_app

CSS

CSS 프레임워크를 쓰고 싶어서 bulma를 설치 했다.

1
npm i bulma -S

글로벌 CSS는 src/App.vue에 import를 하면 되었다.

1
2
3
4
5
6
7
<script>
import "bulma/css/bulma.css";

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

.prettierrc.js

자동 줄맞춤으로 prettier를 쓰고 있는데 줄맞춤할 때마다 script에 ‘가 “로 변환 되고

마지막 ,가 사라져서 lint에 걸려 가지고 고통 받다가 검색을 통해 해결법을 찾음

.prettierrc.js 생성

1
2
3
4
module.exports = {
singleQuote: true,
trailingComma: "all",
};

달력 만들기

이전에 date-picker등은 자주 써봤으나 달력 구현은 이번이 처음이였다.

먼저 요일을 고정이니 반복문이 필요 했는데 v-for 지시자를 통해 테이블의 헤더에 삽입 하였다.

책에서 학습 할 때는 v-for만 적었었는데 key라는 속성을 안 넣어주면 lint 오류가 떴었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<section class="section">
<div class="container">
<table class="table has-text-centered is-fullwidth">
<thead>
<th v-for="day in days" :key="day">{{ day }}</th>
</thead>
</table>
</div>
</section>
</template>

<script>
export default {
data() {
return {
days: [
'일요일',
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
],
}
}
}
</script>

달력의 날짜들을 어떻게 구할지 고민을 하다가 이차원 배열에 1차 배열은 주로 잡고 2차 배열은

날짜들을 담으면 되겠다는 생각이 들었고 다른 캘린더를 보니 테이블에 그릴 때 1일이

어느 요일에서 시작 하는지 알아야 했으며 그 전에 남은 셀이 있다면 저번 달의 날짜들을

채웠어야 했고 마지막 주에 남은 셀이 있으면 다음 달의 날짜를 채웠어야 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<template>
<section class="section">
<div class="container">
<h2 class="subtitle has-text-centered">
{{ year }}년 {{ month }}월 <!-- 현재 달력의 년, 월 표시 -->
</h2>
<table class="table has-text-centered is-fullwidth">
<thead>
<th v-for="day in days" :key="day">{{ day }}</th>
</thead>
<tbody>
<tr v-for="(date, idx) in dates" :key="idx">
<td
v-for="(day, secondIdx) in date" :key="secondIdx"
>
{{ day }}
</td>
</tr>
</tbody>
</table>
</div>
</section>
</template>

<script>
export default {
data() {
return {
days: [
'일요일',
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
],
dates: [],
currentYear: 0,
currentMonth: 0,
year: 0,
month: 0,
};
},
created() { // 데이터에 접근이 가능한 첫 번째 라이프 사이클
const date = new Date();
this.year = date.getFullYear();
this.month = date.getMonth() + 1;
this.calendarData();
},
methods: {
calendarData() {
const [
monthFirstDay,
monthLastDate,
lastMonthLastDate,
] = this.getFirstDayLastDate(this.year, this.month);
this.dates = this.getMonthOfDays(
monthFirstDay,
monthLastDate,
lastMonthLastDate,
);
},
getFirstDayLastDate(year, month) {
const firstDay = new Date(year, month - 1, 1).getDay(); // 이번 달 시작 요일
const lastDate = new Date(year, month, 0).getDate(); // 이번 달 마지막 날짜
let lastYear = year;
let lastMonth = month - 1;
if (month === 1) {
lastMonth = 12;
lastYear -= 1;
}
const prevLastDate = new Date(lastYear, lastMonth, 0).getDate(); // 지난 달 마지막 날짜
return [firstDay, lastDate, prevLastDate];
},
getMonthOfDays(
monthFirstDay,
monthLastDate,
prevMonthLastDate,
) {
let day = 1;
let prevDay = (prevMonthLastDate - monthFirstDay) + 1;
const dates = [];
let weekOfDays = [];
while (day <= monthLastDate) {
if (day === 1) {
// 1일이 어느 요일인지에 따라 테이블에 그리기 위한 지난 셀의 날짜들을 구할 필요가 있다.
for (let j = 0; j < monthFirstDay; j += 1) {
weekOfDays.push(prevDay);
prevDay += 1;
}
}
weekOfDays.push(day);
if (weekOfDays.length === 7) {
// 일주일 채우면
dates.push(weekOfDays);
weekOfDays = []; // 초기화
}
day += 1;
}
const len = weekOfDays.length;
if (len > 0 && len < 7) {
for (let k = 1; k <= 7 - len; k += 1) {
weekOfDays.push(k);
}
}
if (weekOfDays.length > 0) dates.push(weekOfDays); // 남은 날짜 추가
return dates;
},
},
};
</script>

특별할 거 없이 여전히 v-for만을 이용해서 달력을 그리기까지 완성 하였다.

이후에는 버튼을 통해 이전 달, 다음 달의 캘린더, 그리고 현재 달력의 오늘 날짜, 지난 달의

날짜, 다음 달의 날짜들은 색깔을 주고 싶어서 시도를 해 보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<template>
<section class="section">
<div class="container">
<h2 class="subtitle has-text-centered">
<button class="button is-small is-primary is-outlined mr-5"
@click="calendarData(-1)">&lt;</button>
{{ year }}년 {{ month }}월
<button class="button is-small is-primary is-outlined ml-5"
@click="calendarData(1)">&gt;</button>
</h2>
<table class="table has-text-centered is-fullwidth">
<thead>
<th v-for="day in days" :key="day">{{ day }}</th>
</thead>
<tbody>
<tr v-for="(date, idx) in dates" :key="idx">
<td
v-for="(day, secondIdx) in date"
:key="secondIdx"
:class="{ 'has-text-info-dark': idx === 0 && day >= lastMonthStart,
'has-text-danger': dates.length - 1 === idx && nextMonthStart > day,
'has-text-primary': day === today && month === currentMonth && year === currentYear
}"
>
{{ day }}
</td>
</tr>
</tbody>
</table>
</div>
</section>
</template>

<script>
export default {
data() {
return {
days: [
'일요일',
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
],
dates: [],
currentYear: 0,
currentMonth: 0,
year: 0,
month: 0,
lastMonthStart: 0,
nextMonthStart: 0,
today: 0,
};
},
created() { // 데이터에 접근이 가능한 첫 번째 라이프 사이클
const date = new Date();
this.currentYear = date.getFullYear(); // 이하 현재 년, 월 가지고 있기
this.currentMonth = date.getMonth() + 1;
this.year = this.currentYear;
this.month = this.currentMonth;
this.today = date.getDate(); // 오늘 날짜
this.calendarData();
},
methods: {
calendarData(arg) { // 인자를 추가
if (arg < 0) { // -1이 들어오면 지난 달 달력으로 이동
this.month -= 1;
} else if (arg === 1) { // 1이 들어오면 다음 달 달력으로 이동
this.month += 1;
}
if (this.month === 0) { // 작년 12월
this.year -= 1;
this.month = 12;
} else if (this.month > 12) { // 내년 1월
this.year += 1;
this.month = 1;
}
const [
monthFirstDay,
monthLastDate,
lastMonthLastDate,
] = this.getFirstDayLastDate(this.year, this.month);
this.dates = this.getMonthOfDays(
monthFirstDay,
monthLastDate,
lastMonthLastDate,
);
},
getFirstDayLastDate(year, month) {
const firstDay = new Date(year, month - 1, 1).getDay(); // 이번 달 시작 요일
const lastDate = new Date(year, month, 0).getDate(); // 이번 달 마지막 날짜
let lastYear = year;
let lastMonth = month - 1;
if (month === 1) {
lastMonth = 12;
lastYear -= 1;
}
const prevLastDate = new Date(lastYear, lastMonth, 0).getDate(); // 지난 달 마지막 날짜
return [firstDay, lastDate, prevLastDate];
},
getMonthOfDays(
monthFirstDay,
monthLastDate,
prevMonthLastDate,
) {
let day = 1;
let prevDay = (prevMonthLastDate - monthFirstDay) + 1;
const dates = [];
let weekOfDays = [];
while (day <= monthLastDate) {
if (day === 1) {
// 1일이 어느 요일인지에 따라 테이블에 그리기 위한 지난 셀의 날짜들을 구할 필요가 있다.
for (let j = 0; j < monthFirstDay; j += 1) {
if (j === 0) this.lastMonthStart = prevDay; // 지난 달에서 제일 작은 날짜
weekOfDays.push(prevDay);
prevDay += 1;
}
}
weekOfDays.push(day);
if (weekOfDays.length === 7) {
// 일주일 채우면
dates.push(weekOfDays);
weekOfDays = []; // 초기화
}
day += 1;
}
const len = weekOfDays.length;
if (len > 0 && len < 7) {
for (let k = 1; k <= 7 - len; k += 1) {
weekOfDays.push(k);
}
}
if (weekOfDays.length > 0) dates.push(weekOfDays); // 남은 날짜 추가
this.nextMonthStart = weekOfDays[0]; // 이번 달 마지막 주에서 제일 작은 날짜
return dates;
},
},
};
</script>

클래스를 제어하기 위해서는 :class 지시자를 사용 했다.

오늘 날짜는 별도로 가지고 있다가 해당 조건일 때만 색을 변경 하였고, 현재 달력에서 지난 달의

날짜는 첫 주에서만 가지고 있으니 첫 주의 제일 작은 수를 가지고 있다가 그 수보다 큰 경우에만

색을 변경해 주었고 마찬가지로 현재 달력에서 다음 달의 날짜는 마지막 주에서 0번째의 날짜를

가지고 있다가 그 수보다 작은 경우에만 색을 변경해 주었다.

다음 목표로는 일정을 추가 하고 볼 수 있게 만들어 봐야 겠다.

참고

[Vue.JS] Vue-CLI 3 시작하기
Single quotes are being replaced with double quotes


Prev
2020-09-03 23:14:41
Next