목표

  • 투두리스트 상세페이지 만들기
  • useRoute()로 파라미터 가져오기
  • useFetch()로 서버 데이터 가져오기

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. 상세페이지 만들기

  • pages/[id].vue
  1. pages 디렉터리 내에 [id].vue 파일을 만든다. 별도의 설정 없이 자동으로 동적 라우팅이 생성된다.

투두리스트 목록 페이지 = index.vue

투두리스트 상세 페이지 = [id].vue

 

Nuxt3의 핵심 기능 중 하나는 파일 시스템 라우터이다.

pages/ 디렉터리 내의 모든 Vue 파일은 URL을 생성한다.

 

https://nuxt.com/docs/getting-started/routing

라우팅에 대한 자세한 설명은 공식문서를 참고한다.

 

주의

Vue나 Nuxt2에서는 동적 라우팅 페이지가 _id.vue(언더바 _) 였다. Nuxt3부터는 [id].vue(대괄호 []) 이다.

 

2. 파라미터 가져오기

  • pages/[id].vue
  1. route 변수를 만든다.
  2. ${route.params.id}로 url의 파라미터 값을 가져온다.
  3. useFetch()로 해당 파라미터에 대한 서버 데이터를 가져온다.

 

https://nuxt.com/docs/api/composables/use-route

useRoute()에 대한 자세한 설명은 공식 문서를 확인한다.

<script setup lang="ts">
const route = useRoute()
console.log(route)

const { data } = await useFetch(`http://localhost:3001/todo/${route.params.id}`)
console.log(data)
</script>

 

3. 마크업

  • pages/[id].vue
  1. 테일윈드를 사용해 CSS를 작성했다.
  2. { data }의 id와 todo 값을 화면에 보여준다.
<script setup lang="ts">
const route = useRoute()

const { data } = await useFetch(`http://localhost:3001/todo/${route.params.id}`)
</script>

<template>
  <section>
    <h1 class="mt-6 mb-12 md:mt-10 text-5xl font-bold text-center">To Do Detail</h1>

    <div class="p-4 md:p-6 md:pb-12 border rounded">
      <p class="block mb-6 text-xl font-bold text-right">No.{{ data.id }}</p>
      <p class="flex gap-2 text-base md:text-lg">{{ data.todo }}</p>
    </div>

    <NuxtLink to="/"  class="block w-full md:w-1/3 m-auto mt-12 px-3 py-4 bg-teal-950 hover:bg-teal-500 transition rounded text-center">
      Go to To Do List
    </NuxtLink >
  </section>
</template>

목표

  • useFetch()로 서버 데이터 가져오기
  • watch()를 사용해 서버 데이터 변경 감지하기
  • $fetch 사용해 HTTP 메서드 사용하기 (GET, POST, DELETE)
  • 서버 데이터를 등록순/최신순 정렬하기

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. Nest.js + PSQL (백앤드 + 데이터베이스)

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

2. toDo 가져오기

  • pages/index.vue

Nuxt3에서는 axios 대신 useFetch()를 사용해 데이터 패칭을 한다.

useFetch()는 데이터를 비동기적으로 불러올 수 있고, 기존 axios 코드보다 로직을 간소화할 수 있다.

 

https://jae-study.tistory.com/127

useFetch()에 대한 자세한 설명은 위의 링크를 참고한다.

 

  1. Nuxt3는 타입스크립트 기반이기 때문에 useFetch()로 받아오는 데이터의 타입을 정의한다. (IToDo[])
  2. 서버에서 받은 데이터를 가공하기 위해 toDoData 변수를 만든다. useFetch()로 받아오는 데이터의 타입은 기본적으로 객체(object)이다. 이것을 배열로 만들어 정렬(sort 메서드)을 하고 싶어 toDoData 변수를 만들었다.
  3. toDo를 추가하고 삭제할 때마다 실시간으로 렌더링 하기 위해 watch 함수를 사용한다.
<script setup lang="ts">
interface IToDo {
  id: number
  todo: string
  created_at: Date
}

// axios.get 기능 (toDo 가져오기)
const { data, refresh } = await useFetch<IToDo[]>('http://localhost:3001/todo')
const toDoData = ref(data.value ? [...data.value] : [])

watch(() => data.value, () => {
  toDoData.value = isActive.value ? [...data.value] : [...data.value].reverse()
})

...
</script>

 

3. toDo 추가하기

  • pages/index.vue
  1. newToDo 변수를 만들어 빈 값이면 alert() 창이 나오게 하고, 값이 있으면 POST 메서드를 실행한다.
  2. POST가 완료되면 newToDo를 빈 값으로 만들고, 실시간 값 변화를 감지하기 위해 refresh()를 실행한다.
<script setup lang="ts">
const newToDo = ref('')

// axios.post 기능 (toDo 추가하기)
async function addToDo() {
  if(newToDo.value === '') {
    alert('할 일을 입력해 주세요.')
  } else {
    await $fetch('http://localhost:3001/todo', {
      method: 'POST',
      body: { todo: newToDo.value }
    })

    newToDo.value = ''
    await refresh()
  }
}

...
</script>

 

4. toDo 삭제하기

  • pages/index.vue
  1. 리스트 1개만 삭제하고 싶으면 해당하는 id 값을 매개변수로 받아 DELETE 메서드를 실행한다.
  2. 리스트 전체를 삭제하고 싶으면 api 전체 리스트에서 DELETE 메서드를 실행한다.
  3. DELETE가 완료되면 실시간 값 변화를 감지하기 위해 refresh()를 실행한다.
<script setup lang="ts">
// axios.delete 기능 (toDo 삭제하기)
async function deleteToDo(id: number) {
  await $fetch(`http://localhost:3001/todo/${id}`, {
    method: 'DELETE'
  })

  await refresh()
}

// axios.delete 기능 (toDo 전체 삭제하기)
async function clearToDo() {
  if(window.confirm('리스트를 모두 삭제하시겠습니까?')) {
    await $fetch(`http://localhost:3001/todo`, {
      method: 'DELETE'
    })

    await refresh()
  }
}

...
</script>

 

5. toDo 정렬하기

  • pages/index.vue
  1. useFetch()로 받은 데이터를 배열로 만들어 sort() 메서드를 사용해 등록순/최신순으로 정렬한다.
<script setup lang="ts">
const isActive = ref(true)

// 등록순, 최신순 정렬하기
function sortToDo(compareFn: (a: IToDo, b: IToDo) => number) {
  toDoData.value = [...data.value].sort(compareFn)
  isActive.value = !isActive.value
}

...
</script>

<template>
  <section class="w-full max-w-screen-lg min-h-screen m-auto">
    ...

    <div v-if="toDoData.length > 0">
      <div class="flex gap-2 mb-6">
        <button
          :class="{ 'underline underline-offset-4' : isActive }"
          @click="() => sortToDo((a, b) => a.id - b.id)">
          등록순
        </button>
        <i>|</i>
        <button
          :class="{ 'underline underline-offset-4' : !isActive }"
          @click="() => sortToDo((a, b) => b.id - a.id)">
          최신순
        </button>
      </div>
    </div>
    
    ...
  </section>
</template>

 

6. 마크업

  • pages/index.vue
  1. 테일윈드를 사용해 CSS를 작성했다.
  2. 각각의 버튼에 맞는 @click 이벤트를 작성한다.
<template>
  <section class="w-full max-w-screen-lg min-h-screen m-auto">
    <h1 class="mt-6 mb-12 md:mt-10 text-5xl font-bold text-center">To Do List</h1>

    <form
      class="mb-6"
      @submit.prevent="addToDo()">
      <label
        for="toDo"
        class="block mb-3 text-xl font-bold">
        New To Do
      </label>

      <div class="flex gap-3 md:gap-5">
        <input
          v-model="newToDo"
          type="text"
          id="toDo"
          class="w-3/4 px-3 py-4 rounded outline-none text-black"
          placeholder="할 일을 입력해 주세요."/>
        <button class="w-1/4 bg-teal-600 hover:bg-teal-500 transition rounded">추가하기</button>
      </div>
    </form>

    <div v-if="toDoData.length > 0">
      <div class="flex gap-2 mb-6">
        <button
          :class="{ 'underline underline-offset-4' : isActive }"
          @click="() => sortToDo((a, b) => a.id - b.id)">
          등록순
        </button>
        <i>|</i>
        <button
          :class="{ 'underline underline-offset-4' : !isActive }"
          @click="() => sortToDo((a, b) => b.id - a.id)">
          최신순
        </button>
      </div>

      <ul>
        <li
            v-for="(data, key) in toDoData"
            :key="key"
            class="flex justify-between items-center gap-3 mb-6 px-4 py-3 md:px-5 border rounded">
          <p class="flex gap-2 text-base md:text-lg">
            <span>{{ key + 1 }}.</span> {{ data.todo }}
          </p>

          <button
            class="min-w-fit px-5 py-2 bg-teal-600 hover:bg-teal-500 transition rounded"
            @click="deleteToDo(data.id)">
            삭제
          </button>
        </li>
      </ul>

      <button
        class="block w-full md:w-1/3 m-auto mt-12 px-3 py-4 bg-teal-950 hover:bg-teal-500 transition rounded"
        @click="clearToDo()">
        전체 삭제하기
      </button>
    </div>
  </section>
</template>

 


상세 코드는 깃허브 페이지를 참고한다.

 

https://github.com/heejae0811/nuxt3-todo/tree/axios

 

GitHub - heejae0811/nuxt3-todo: main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch)

main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch) - GitHub - heejae0811/nuxt3-todo: main: Pinia To Do List / axios: Nest.js + PSQL To Do List (useFetch)

github.com

 

https://github.com/heejae0811/todo-backend

 

GitHub - heejae0811/todo-backend: Nest.js + PSQL ToDo 백앤드

Nest.js + PSQL ToDo 백앤드. Contribute to heejae0811/todo-backend development by creating an account on GitHub.

github.com

결과 화면

https://heejae0811.github.io/nuxt3-todo/

 

오늘의 할 일

오늘의 할 일은 무엇인가요?

heejae0811.github.io

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. nuxt.config.ts

head 태그 아래에 추가하고 싶은 메타태그를 작성한다. (title, meta, link가 HTML 태그로 변환된다.)

name, content, property 등 작성한 값들이 메타태그 속성으로 들어가기 때문에 다른 메타태그를 추가하고 싶다면 속성에 맞는 값들을 입력한다.

 

메타태그 이미지와 파비콘 이미지 경로는 절대 경로를 사용한다.

깃허브 페이지를 통해 빌드된 파일을 배포하고 있기 때문에 메타태그 이미지는 assets 폴더가 아닌 public 폴더에 넣는다.

export default defineNuxtConfig({
  app: {
    head: {
      title: 'ToDo List',
      meta: [
        { name: 'description', content: '오늘의 할 일은 무엇인가요?' },
        { property: 'og:title', content: '오늘의 할 일' },
        { property: 'og:description', content: '오늘의 할 일은 무엇인가요?' },
        { property: 'og:image', content: 'https://heejae0811.github.io/nuxt3-todo/meta.jpg' }
      ],
      link: [
        { rel: 'icon', type: 'image/x-icon', href: 'https://heejae0811.github.io/nuxt3-todo/favicon.ico' }
      ]
    },
  },
  
  ...
})

 

2. 페이지 별 메타태그 설정하기

nuxt.config.ts에 정의한 메타태그는 모든 페이지에서 공통으로 적용된다.

 

각 페이지 별로 메타태그를 다르게 설정하고 싶으면 아래 링크를 참고한다.

https://jae-study.tistory.com/129

 

[Nuxt3] #16 Nuxt 3 SEO (메타태그 설정하기)

SEO & metas with Nuxt 3 — Course part 16 https://www.youtube.com/watch?v=PpyXtoM5HWQ&list=PL8HkCX2C5h0XT3xWYn71TlsAAo0kizmVc&index=16 유튜브 강의를 참고하고 있습니다. 공통 메타태그 설정하기 nuxt.config.ts 모든 페이지에서

jae-study.tistory.com

결과 화면

https://heejae0811.github.io/nuxt3-todo/

 

오늘의 할 일

오늘의 할 일은 무엇인가요?

heejae0811.github.io

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. gh-pages 설치하기

터미널에서 아래 명령어를 실행한다.

설치된 버전은 package.json 파일에서 확인할 수 있다.

npm install gh-pages

또는

yarn add gh-pages

 

2. package.json

package.json 파일의 scripts 부분에 "deploy": "~~"를 추가한다.

-f 옵션은 gh-pages 브랜치 깃 히스토리를 삭제한다. 깃 히스토리를 남기고 싶다면 해당 옵션을 제외한다.

"scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
    "deploy": "gh-pages --dotfiles -d .output/public -f"
},

 

3. nuxt.config.ts

nuxt.config.ts 파일의 app 부분에 baseUrl과 buildAssetsDir를 추가한다.

이때 baseUrl은 깃허브 레포지토리 이름으로 한다.

export default defineNuxtConfig({
  app: {
    baseURL: '/깃 레포지토리 이름/',
    buildAssetsDir: 'assets'
  },
  
  ...
})

 

4.  깃허브 페이지 배포하기

터미널에서 npm run generate 명령어를 실행하면 .output 폴더와 dist 폴더가 생기는 것을 볼 수 있다.

npm run deploy 명령어를 실행하면 깃허브에 배포된다.

npm run generate
npm run deploy

 

5. 깃허브 Settings

위의 과정을 수행했음에도 깃허브 페이지가 배포가 되지 않는다면 깃 레포지토리 Settings을 확인해 본다.

깃 레포지토리 - Settings - Pages에서 Branch가 gh-pages로 되어있는지 확인한다.

목표

  • sort 메서드를 이용해 toDoList 정렬하기
  • v-bind를 이용해 class 바인딩하기

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

0. 정렬 함수 하나로 만들기 (23.12.27 추가)

  • pages/index.vue

중복된 코드가 많고, 토글 형태로 isActive 변수를 만들어 등록순을 클릭하고 또 등록순을 클릭하면 최신순으로 변경되는 오류가 발생해 코드를 수정했다.

 

  1. sortToDo() 함수를 만든다.
  2. compareFn 매개변수를 만들어 @click 이벤트에서 받아온다. Nuxt3은 타입스크립트 기반이기 때문에 타입을 작성한다.
  3. data.value는 axios에서 받아오는 toDoData이다. 객체 형태이기 때문에 전개연산자를 사용했지만, 배열이라면 바로 sort 메서드를 사용한다.
  4. @click 이벤트에서 isActive를 true/false로 바꿔 class를 바인딩한다.
<script setup lang="ts">
...

interface IToDo {
  id: number
  todo: string
  created_at: Date
}

function sortToDo(compareFn: (a: IToDo, b: IToDo) => number) {
  toDoData.value = [...data.value].sort(compareFn)
}
</script>

<template>
    <section>
    	...
    
        <div v-if="toDoData.length > 0">
            <div class="flex gap-2 mb-6">
                <button
                    :class="{ 'underline underline-offset-4': isActive }"
                    @click="() => {
                    isActive = true
                    sortToDo((a, b) => a.id - b.id)}">
                    등록순
                </button>
                <i>|</i>
                <button
                    :class="{ 'underline underline-offset-4': !isActive }"
                    @click="() => {
                    isActive = false
                    sortToDo((a, b) => b.id - a.id)}">
                    최신순
                </button>
            </div>
        </div>
        
        ...
    </section>
</template>

 

1. 등록순/최신순 정렬 함수 만들기

  • pages/index.vue
  1. sort 메서드로 toDoList의 id를 비교해 등록순/최신순 정렬 함수를 만든다.
  2. <button> 태그에 @click 이벤트를 걸어준다.
<script setup lang="ts">
...

function sortRegistered() {
  toDo.toDoList.sort((a, b) => a.id - b.id)
}

function sortLatest() {
  toDo.toDoList.sort((a, b) => b.id - a.id)
}
</script>

<template>
  <section class="w-full max-w-screen-lg m-auto">
    ...
  
    <div v-if="toDo.toDoList.length > 0">
      <div class="flex gap-2">
        <button @click="sortRegistered()">등록순</button>
        <i>|</i>
        <button @click="sortLatest()">최신순</button>
      </div>
      
      ...
    </div>
  </section>
</template>

 

2. v-bind로 class 바인딩하기

  • pages/index.vue
  1. isActive 변수를 Boolean으로 만든다. (등록순/최신순 버튼에 on/off 효과를 주기 위해서)
  2. sort 함수가 동작할 때마다 isActive 변수가 true/false 될 수 있게 만든다.
  3. v-bind( : )를 이용해 클래스를 바인딩한다. (1개의 클래스만 바인딩하며 ''이 없어도 되지만, 여러 개 클래스를 바인딩할 경우 '' 안에 작성한다.)
<script setup lang="ts">
...

let isActive = true

function sortRegistered() {
  toDo.toDoList.sort((a, b) => a.id - b.id)
  isActive = !isActive
}

function sortLatest() {
  toDo.toDoList.sort((a, b) => b.id - a.id)
  isActive = !isActive
}
</script>

<template>
  <section class="w-full max-w-screen-lg m-auto">
    ...
    
    <div v-if="toDo.toDoList.length > 0">
      <div class="flex gap-2">
        <button
          :class="{ 'underline underline-offset-4' : isActive }"
          @click="sortRegistered()">등록순</button>
        <i>|</i>
        <button
          :class="{ 'underline underline-offset-4' : !isActive }"
          @click="sortLatest()">최신순</button>
      </div>
      
      ...
    </div>
  </section>
</template>

 

목표

  • Pinia 설치하기
  • Pinia store에서 데이터 불러오기
  • toDo 추가 및 삭제하기

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. Pinia 설치하기

https://jae-study.tistory.com/123

 

[Nuxt3] #10 Nuxt 3 State Management (일반 변수, useState, pinia 상태 관리 비교)

State management with Nuxt 3 — Course part 10 https://www.youtube.com/watch?v=IkpoAKS1s-k&list=PL8HkCX2C5h0XT3xWYn71TlsAAo0kizmVc&index=10 유튜브 강의를 참고하고 있습니다. State Management란 상태 관리(State Management)에서 상태(Sta

jae-study.tistory.com

Pinia 및 Nuxt3 state에 대한 자세한 설명은 위의 링크를 참고한다.

 

2. json 데이터 만들기

  • server/api/toDoList.json

api가 없기 때문에 json 파일로 빈 배열을 만든다.

[]

 

3. Pinia store 만들기

  • stores/toDoStore.ts
  1. pinia를 import 한다.
  2. 만든 toDoList.json 파일을 import 한다.
  3. useToDoStore 변수를 만들고, state를 정의한다. (초기값은 toDoList.json 파일의 빈 배열)
  4. Nuxt3은 타입스크립트 기반이기 때문에 interface을 이용해 state의 타입을 정의한다. (배열 안에 객체가 있는 형태)
import { defineStore } from 'pinia'
import toDoList from '../server/api/toDoList.json'

interface ItoDo {
  id: number,
  content: string
}

export const useToDoStore = defineStore('toDo', {
  state: () => ({
    toDoList: toDoList as ItoDo[]
  }),

  actions: {}
})

 

4. actions 정의하기

4-1. toDoList 추가하기 (addToDo())

  1. 입력한 toDo(content)를 배열에 push한다. 
  2. 이때 id는 고유한 값을 나타내기 위해 현재 시간(Date.now())으로 한다.

 

4-2. toDoList 삭제하기 (deleteToDo())

  1. findIndex 메서드와 id를 이용해 삭제할 toDo의 index를 찾는다.
  2. splice 메서드를 이용해 배열에서 해당 index의 toDo를 자른다.

 

4-3. toDoList 전체 삭제하기 (clearToDo())

  1. toDoList를 빈 배열로 만든다.
  2. 전체 삭제이기 때문에 window.confirm 창을 통해 알럿 메세지를 보여준다.
...

export const useToDoStore = defineStore('toDo', {
  state: () => ({
    toDoList: toDoList as ItoDo[]
  }),

  actions: {
    addToDo(content: string) {
      this.toDoList.push({ id: Date.now(), content: content })
    },

    deleteToDo(id: number) {
      const index = this.toDoList.findIndex(list => list.id === id)

      if(index > -1) {
        this.toDoList.splice(index, 1)
      }
    },

    clearToDo() {
      if(window.confirm('리스트를 모두 삭제하시겠습니까?')) {
        this.toDoList = []
      }
    }
  }
})

 

5. 페이지에서 Pinia store 불러오기

  • pages/index.vue
  1. toDoStore를 import 한다.
  2. newToDo 변수를 만들고, v-model를 이용해 input에 입력한 내용이 양방향 바인딩 될 수 있게 한다.
  3. Pinia store에 만든 actions을 클릭 이벤트로 만들어 toDo를 추가, 삭제, 전체 삭제한다. (toDo.addToDo(), toDo.deleteToDo(), toDo.clearToDo())
<script setup lang="ts">
import { useToDoStore } from '@/stores/toDoStore'

const toDo = useToDoStore()
const newToDo = ref('')

function addToDo() {
  if(newToDo.value) {
    toDo.addToDo(newToDo.value)
    newToDo.value = ''
  }
}
</script>

<template>
  <section class="w-full max-w-screen-lg m-auto">
    <h1 class="mb-10 text-5xl font-bold text-center">ToDo List</h1>

    <form
        class="mb-5"
        @submit.prevent="addToDo()">
      <label for="toDo" class="block mb-3 text-xl font-bold">New ToDo</label>

      <div class="flex justify-between gap-5">
        <input
            v-model="newToDo"
            type="text"
            id="toDo"
            class="w-3/4 px-3 py-4 rounded text-black"
            placeholder="할 일을 입력해 주세요."/>
        <button class="w-1/4 bg-slate-500 rounded">추가하기</button>
      </div>
    </form>

    <div v-if="toDo.toDoList.length > 0">
      <div class="flex gap-2">
        <button>등록순</button>
        <i>|</i>
        <button>최신순</button>
      </div>

      <ul>
        <li
            v-for="(list, key) in toDo.toDoList"
            :key="key"
            class="flex justify-between items-center gap-5 mt-5 px-5 py-2 border rounded">
          <p class="flex gap-5 text-lg">
            <span>{{ key + 1 }}.</span> {{ list.content }}
          </p>
          <button
              @click="toDo.deleteToDo(list.id)"
              class="px-5 py-2 bg-slate-500 rounded">
            삭제
          </button>
        </li>
      </ul>

      <button
          @click="toDo.clearTodo()"
          class="block w-1/3 mt-10 m-auto px-3 py-4 bg-slate-400 rounded">
        전체 삭제하기
      </button>
    </div>
  </section>
</template>

 

toDoStore를 콘솔로 확인하면 store에 만든 state와 action 등 모든 정보를 확인할 수 있다.

목표

  • Nuxt3 설치 및 테일윈드 CSS 설치
  • 투두 리스트 마크업
  • 투두 리스트 테일윈드 css 적용

 


결과 화면

 


버전

node v.20.8.1

nuxt v.3.8.1

vue v.3.3.8

 

1. Nuxt3 설치 및 테일윈드 CSS 설치하기

https://jae-study.tistory.com/114

 

[Nuxt3] #1 Nuxt 3 설치 및 테일윈드 CSS 적용하기

Create an app with Nuxt 3 — Course part 1 https://www.youtube.com/watch?v=hj3NNlTqIJg&list=PL8HkCX2C5h0XT3xWYn71TlsAAo0kizmVc&index=1 유튜브 강의를 참고하고 있습니다. Nuxt는 Vue.js를 사용하여 안전하고 성능이 뛰어난 풀 스

jae-study.tistory.com

Nuxt3 설치 및 테일윈드 CSS 설치에 대한 설명은 위의 링크를 참고한다.

 

 

2. 테일윈드 CSS를 이용해서 마크업 하기

  • pages/index.vue
  1. 새로운 toDo를 입력할 <form> 태그를 만들고, 입력한 toDo들이 나타날 수 있게 <ul> 태그로 목록을 만든다.
  2. 테일윈드를 사용해 CSS를 적용한다.
  3. toDoList 변수와 v-for를 이용해 toDoList를 보여준다.
<script setup lang="ts">
const toDoList = [
  {
    id: 1,
    content: '첫번째 할 일'
  },
  {
    id: 2,
    content: '두번째 할 일'
  },
  {
    id: 3,
    content: '세번째 할 일'
  }
]
</script>

<template>
  <section class="w-full max-w-screen-lg m-auto">
    <h1 class="mb-10 text-5xl font-bold text-center">ToDo List</h1>

    <form class="mb-5">
      <label for="toDo" class="block mb-3 text-xl font-bold">New ToDo</label>

      <div class="flex justify-between gap-5">
        <input
            type="text"
            id="toDo"
            class="w-3/4 px-3 py-4 rounded text-black"
            placeholder="할 일을 입력해 주세요."/>
        <button class="w-1/4 bg-slate-500 rounded">추가하기</button>
      </div>
    </form>

    <div>
      <div class="flex gap-2">
        <button>등록순</button>
        <i>|</i>
        <button>최신순</button>
      </div>

      <ul>
        <li
            v-for="(list, key) in toDoList"
            :key="key"
            class="flex justify-between items-center gap-5 mt-5 px-5 py-2 border rounded">
          <p class="flex gap-5 text-lg">
            <span>{{ key + 1 }}.</span> {{ list.content }}
          </p>
          <button class="px-5 py-2 bg-slate-500 rounded">
            삭제
          </button>
        </li>
      </ul>

      <button class="block w-1/3 mt-10 m-auto px-3 py-4 bg-slate-400 rounded">
        전체 삭제하기
      </button>
    </div>
  </section>
</template>

+ Recent posts