프로그래밍/Vue.js

[Vue.js] 간단한 todo app 구현

abfc 2018. 2. 22.
반응형





프로젝트 폴더 구조



기능

  1. 입력폼에 데이터 입력 후 엔터 혹은 add 버튼 클릭 시 데이터 추가

  2. 탭 [todo, finish] 클릭 시 해당 목록 출력

  3. todo 목록에서 완료버튼 클릭 시 finish로 이동

  4. finish 목록에서 reset버튼 클릭 시 todo로 이동





vue-cli 설치(전역)

sudo npm install vue-cli



'todo-app' 폴더 생성 후 webpack-simple 템플릿으로 프로젝트 생성

mkdir todo-app

cd todo-app

vue init webpack-simple



전부 enter(기본값)

npm install

npm run dev






프로젝트 생성 후 폴더 구조









todo-app/style.css(생성 후 작성)


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
body, ul {
  margin: 0;
  padding: 0;
}
ul {
  list-style: none;
}
img {
  width: 100%;
}
.container {
  margin: 0 15px 0 15px;
}
header {
  border-bottom: 1px #ccc solid;
  padding: 10px 0  10px 0;
  text-align: center;
}
input[type=text] {
  display: block;
  box-sizing: border-box;
  width: 100%;
  margin: 15px 0 15px 0;
  padding: 10px 15px;
  font-size: 14px;
  line-height: 1.5;
  border: 1px solid #cccccc;
}
.content {
  border: 1px solid #ccc;
}
ul.tabs {
  display: flex;
}
.tabs li {
  display: inline-block;
  width: 50%;
  padding: 15px;
  text-align: center;
  box-sizing: border-box;
  border-bottom: 1px solid #ccc;
  background-color: #eee;
  color: #999;
}
.tabs li.active {
  /* background-color: #2ac1bc; */
  background-color: #045FB4;
  color: #fff;
}
.list li {
  box-sizing: border-box;
  display: block;
  padding: 15px;
  border-bottom: 1px solid #ccc;
  position: relative;
}
.list li:last-child {
  border-bottom: none;
}
.list li .number{
  margin-right: 15px;
  color: #ccc;
}
.list li .date{
  position: absolute;
  right: 50px;
  top: 15px;
  margin-right: 15px;
  color: #ccc;
}
.list li .btn-remove{
  position: absolute;
  right: 0px;
  top: 15px;
  margin-right: 15px;
}
form {
  position: relative;
}
.btn-reset,
.btn-remove {
  border-radius: 15%;
  background-color: #ccc;
  color: white;
  border: none;
  padding: 2px 5px;
}
.btn-reset {
  position: absolute;
  top: 12px;
  right: 10px;
}
.btn-reset::before,
.btn-remove::before {
  content: 'add'
}
#search-result li {
  display: flex;
  margin-bottom: 15px;
}
#search-result img {
  width: 30%;
  height: 30%;
}
.todoBtn {
  float: right;
  margin: 2px;
}
 
cs






todo-app/index.html 수정


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>todo-app</title>
    <link rel="stylesheet" href="style.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>
 
cs






src/components/FormComponent.vue


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>
  <form v-on:enter.prevent="addTodo">
    <input type="text" v-model="inputValue" />
    <button class="btn-reset" v-on:click.prevent="addTodo"></button>
    <!-- v-on:click => click 이벤트 리슨 (.prevent 속성을 추가하면 이벤트 전파를 중단함) -->
  </form>
</template>
 
<script>
export default {
  props: ['value'], //부모(App.vue)로부터 받아옴
  data() {
    return {
      inputValue: this.value
    }
  },
  methods: {
    addTodo() {
      //$emit => 부모(App.vue)에게 @submit이라는 이름으로 이벤트 전달
      //this.inputValue 인자로 같이 전달
      this.$emit('@submit', this.inputValue)
 
      //데이터 추가 후 form을 비우기 위해 inputValue값을 비워줌
      this.inputValue = ''
    }
  }
}
</script>
 
cs






src/components/TabComponent.vue


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
  <ul class="tabs">
    <li v-for="tab in tabs" v-bind:class="{active: tab === selectedTab}"
      v-on:click="onClickTabs(tab)">
      <!-- v-bind:class => 선택한 탭에 active class를 지정 -->
      {{tab}}
    </li>
  </ul>
</template>
 
<script>
export default {
  props: ['tabs''selectedTab'], //부모(App.vue)로부터 받아옴 => tabs(todo, finish), selectedTab : 현재 선택한 탭
  methods: {
    onClickTabs(tab) {
      //$emit => 부모(App.vue)에게 @change 이름으로 이벤트 전달
      //tab(선택한 탭) 인자로 같이 전달
      this.$emit('@change', tab)
    }
  }
}
</script>
 
cs






src/components/ListComponent.vue


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
<template>
  <div>
    <ul class="list">
      <li v-for="(item, index) in data">
        <span class="glyphicon glyphicon-asterisk"></span>
        {{item.todo}}
        <span class="todoBtn label label-primary"
          v-if="selectedTab === 'todo'"
          v-on:click="finishBtnClick(index)">
          <span class="glyphicon glyphicon-ok"></span>
        </span>
        <span class="todoBtn label label-danger"
          v-if="selectedTab === 'finish'"
          v-on:click="resetBtnClick(index)">reset</span>
      </li>
    </ul>
  </div>
 
 
</template>
 
<script>
export default {
  props: ['data''selectedTab'],
  methods: {
    finishBtnClick(item) {
      this.$emit('@finish', item)
    },
    resetBtnClick(item) {
      this.$emit('@reset', item)
    }
  }
}
</script>
 
cs







src/models/TodoModel.js


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
export default {
  data: [
    {todo: '할 일 0', state: true},
    {todo: '할 일 1', state: true},
    {todo: '할 일 2', state: false},
    {todo: '할 일 3', state: true},
    {todo: '할 일 4', state: false}
  ],
 
  list(tab) {
    return new Promise(res => {
      if(tab === 'todo') res(this.data.filter(item => item.state === true))
      if(tab === 'finish') res(this.data.filter(item => item.state === false))
    })
  },
 
  add(todo = '') {
    todo = todo.trim()
    if (!todo) return
 
    const state = true
    this.data.push({todo, state})
  },
 
  finish(index) {
    this.data.filter(item => item.state === true)[index].state = false
  },
 
  reset(index) {
    this.data.filter(item => item.state === false)[index].state = true
  },
 
  remove(todo) {
    this.data = this.data.filter(item => item.todo !== todo)
  }
}
 
cs






src/App.vue


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
<template>
  <div>
    <header>
      <h2 calss="container">Todo</h2>
    </header>
 
    <div calss="container">
      <add-form v-bind:value="query" v-on:@submit="onInputTodo"></add-form>
      <!-- FormComponent.vue에서 inputValue에 넣어준 value를 v-bind가 App.vue의 query와 바인딩해줌 -->
      <!-- v-on:@submit => FormComponent.vue에서 $emit으로 전달한 @submit이라는 이벤트를 받아와서 onInputTodo 메서드와 연결 -->
 
      <tab v-bind:tabs="tabs" v-bind:selected-tab="selectedTab" v-on:@change="onClickTab"></tab>
 
    </div>
 
    <div>
      <list v-bind:selected-tab="selectedTab" v-bind:data="todoList"
        v-on:@finish="onClickFinish"
        v-on:@reset="onClickReset"></list>
    </div>
 
  </div>
</template>
 
<script>
//FormComponent 불러옴
import FormComponent from './components/FormComponent.vue'
//TabComponent 불러옴
import TabComponent from './components/TabComponent.vue'
//ListComponent 불러옴
import ListComponent from './components/ListComponent.vue'
 
//Model 불러옴
import TodoModel from './models/TodoModel.js'
 
export default {
  name'app',
  data () {
    return {
      query: '',
      tabs: ['todo''finish'],
      selectedTab: '',
      todoList: []
    }
  },
  created() { //vue 인스턴스가 생성된 후에 실행됨
    this.selectedTab = this.tabs[0//todo 탭 선택
    this.search() //todo list 출력
  },
  components: { //사용할 컴포넌트 등록
    'add-form': FormComponent,
    'tab': TabComponent,
    'list': ListComponent
  },
  methods: {
    search() { //list 검색
      TodoModel.list(this.selectedTab).then(data => {
        this.todoList = data
      })
    },
    onClickTab(tab) { //tab 선택
      this.selectedTab = tab
      this.search()
    },
    onClickFinish(item) { //todo 완료
      TodoModel.finish(item)
      this.search()
    },
    onClickReset(item) { //완료된 todo 리셋
      TodoModel.reset(item)
      this.search()
    },
    onInputTodo(query) { //todo 입력
      TodoModel.add(query)
      this.selectedTab = this.tabs[0]
      this.search()
    }
  }
}
</script>
 
<style>
</style>
 
cs







반응형

'프로그래밍 > Vue.js' 카테고리의 다른 글

Vue.js 기초  (0) 2017.12.26

댓글

💲 추천 글