TodoList CRUD

์ด๋™์–ธยท2024๋…„ 8์›” 26์ผ

new world

๋ชฉ๋ก ๋ณด๊ธฐ
33/62
post-thumbnail

8.26(์›”)

1. toRef

  {{ str1 }} // aaa->zzz
  {{ str2 }} // aaa

const obj = ref({v1:'aaa'})
let str1 = toRef(obj.value, "v1")
let str2 = obj.value.v1

setTimeout(() => {
    obj.value.v1 = 'zzz'
    
}, 3000);

๐Ÿ‘‰ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ณด๊ฒŒ๋˜๋ฉด str1์˜ ๊ฐ’์€ zzz๋กœ ๋ณ€๊ฒฝ์ด ๋˜์ง€๋งŒ, str2๋Š” zzz๋กœ ๋ณ€๊ฒฝ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.
์ด๋Š” str1์€ toRef๋ฅผ ์ด์šฉํ•˜์—ฌ obj.value.v1 ๊ฐ’์„ ๊ฐ„์ ‘์ฐธ์กฐํ•˜๋ฏ€๋กœ ์›๊ฐ’์ด ๋ณ€๊ฒฝ๋ ์‹œ str1๊ฐ’๋„ ๋ณ€๊ฒฝ๋œ๋‹ค.

const userId = ref({mid:''})

const toMid = toRef(userId.value, "mid")

๐Ÿ‘‰ ์ด๋Ÿฌํ•œ ์ ์„ ์ด์šฉํ•˜์—ฌ userId๊ฐ’ ๋˜ํ•œ toRef๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ„์ ‘์ฐธ์กฐ๋ฅผ ํ•œ๋‹ค.




2. Login ๊ตฌํ˜„

2-1. useMember2.js

import { defineStore } from "pinia";
import { computed, ref, toRef } from "vue";


const useMember2 = defineStore('useMember2', ()=> {

    const userId = ref({mid:''})

    const toMid = toRef(userId.value, "mid") // ๊ธฐ์กด์˜ ref๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฐธ์กฐํ•œ๋‹ค๋ฉด 1๋ށ์Šค ๊นŒ์ง€ ์ฐธ์กฐ๊ฐ€ ๋˜์–ด ์ดํ›„ ์ถœ๋ ฅํ• ๋•Œ ์ œ๋Œ€๋กœ๋œ ์ถœ๋ ฅ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
                                            // ์ฝ”๋“œ๋œป์€ userId.value๊ฐ’ ์ค‘์—์„œ mid๊ฐ’์„ ์ฐธ์กฐํ•˜๊ฒ ๋‹ค๋Š” ๋ง.
                                            

    const signin = (str) => {

        userId.value.mid = str
        localStorage.setItem("mid", str)

    }

    const signout = () => {
        userId.value.mid = ''
        localStorage.removeItem("mid")
    }

    const computedMid = computed(()=> { // computed๋Š” ๋‚ด๋ถ€์— ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด๊ฐ€ ์‚ฌ์šฉ๋ ๋•Œ ๊ทธ ๋ฐ˜์‘ํ˜• ๊ฐ์ฒด ๊ฐ’์ด ๋ณ€๊ฒฝ์ด ๋˜๋ฉด ์ถœ๋ ฅ๊ฐ’๋„ ๋ณ€๊ฒฝ๋œ ๊ฐ’์œผ๋กœ ์ถœ๋ ฅ๋œ๋‹ค.

        if(!localStorage.getItem("mid")) {
            return ''
        }

        if(localStorage.getItem("mid")){ 
            userId.value.mid = localStorage.getItem("mid")
        }

        if(toMid){
            return toMid
        }

        return toMid
    }) 

    return {userId,signin,signout,computedMid}
})

export default useMember2




2-2. App.vue

<script setup>
import { RouterLink, RouterView } from 'vue-router';
import useMember2 from './stores/useMember2';

const {signout,computedMid} = useMember2() //useMember.js ์—์„œ member๋ณ€์ˆ˜์™€ logout๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค.
</script>

<template>
  
  <div>
    <span><RouterLink to="/">Main</RouterLink></span> <!--Aํƒœ๊ทธ ์ฒ˜๋Ÿผ ์ƒ๊ธด ํ™”๋ฉด์ด๋™-->
    <span><RouterLink to="/about">About</RouterLink></span>
    <span><RouterLink to="/todo">Todo</RouterLink></span>


    <span v-if="computedMid===''"><RouterLink to="/login">Login</RouterLink></span>
    <span v-if="computedMid!==''"><button @click="signout">Logout</button></span>
  </div>

  <div>
    <hr/>
    USERID : {{ computedMid }}
    <hr/>
  </div>

  <RouterView></RouterView> <!--๋ผ์šฐํ„ฐ๋กœ ์ด์šฉ๋œ ํŽ˜์ด์ง€๋ฅผ ์ถœ๋ ฅ-->
</template>


<style scoped>

span{
  margin-left: 1em;
}

</style>




2-3. LoginPage.vue

<template>
    <div>
        <h1>Login Page</h1>
        <input type="text" v-model="midInput">
        <button @click="handleClick">Login</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import useMember2 from '../stores/useMember2';

const {signin} = useMember2() // useMember.js์˜ login ๊ธฐ๋Šฅ ์‚ฌ์šฉ

const midInput = ref('')

const handleClick = () => {

    const mid = midInput.value // input text ๋กœ ์ž…๋ ฅํ•œ id ๊ฐ’์„ mid๋ณ€์ˆ˜์— ๋„ฃ๊ณ 

    signin(mid) // store์— ์ €์žฅ๋œ signin๋ฉ”์†Œ๋“œ์— mid ์ธ์ž๋ฅผ ๋„ฃ์–ด ํ˜ธ์ถฃ
}

</script>

<style lang="scss" scoped>

</style>




3. paging

3-1. todoListComponent.vue

<template>
    <div>
      <div v-if="loading" class="loadingDiv">
        <h1>Loading.............</h1>
      </div>
  
        <ul>
          <li v-for="t in result.content" :key="t.tno" @click="()=>handleClickMove(t.mno)">
            {{ t }}
          </li>
        </ul>
  
        <template v-for="(p,idx) in pageArr" :key="idx">
          <span class="pageSpan" @click="() => handleClickPage(p.page)" > {{p.label}} </span>
        </template>
  
    </div>
  </template>
  
  <script setup>
  
  import { getList } from '../../apis/todoAPI';
  import useListData from '../../hooks/useListData';
  
  const {loading, moveToRead, router, route, refresh, result, pageArr} = useListData(getList)
  
  const handleClickPage = (pageNum) => {
    
    const currentPage = route.query.page || 1 // page์ฟผ๋ฆฌ๊ฐ’์ด ์žˆ์œผ๋ฉด ๊ฐ€์ ธ์˜ค๊ณ  ์—†์œผ๋ฉด 1
  
    router.push({query: {page:pageNum} }).then(() => {
      if(currentPage == pageNum){ // ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด
        refresh.value = !refresh.value // refresh
      }
      
    })
  }
  
  const handleClickMove = ( tno )=> {
  
    moveToRead(tno)
  }
  
  
  </script>
  
  <style scoped>
  
  .loadingDiv {
    position: absolute;
    top: 30vh;
    left: 40vw;
    width: 20vw;
    height: 10vh;
    background-color: aqua;
  
  }
  
  .pageSpan {
    margin: 0.3em;
    padding: 0.1em;
    border: 1px solid black;
  }
  
  </style>




3-2. useListData.js

import { computed, onMounted, ref, watch } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';


const useListData = (listFn) => {

  const fn = listFn

  const route = useRoute()
  const router = useRouter()
  
  const loading = ref(false)
  const refresh = ref(false)

  const result = ref({
    content:[],
    number:0,
    size:10,
    totalElements:0,
    totalPages:0
  })
  
  const loadPageData = async (page) => {

    loading.value = true
    const data = await fn(page)

    result.value = data
    loading.value = false
    
  }

  const pageArr =  computed(() => {

    //ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ 
    const currentPage = result.value.number + 1

    //๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ  -  ์˜ฌ๋ฆผ( ํŽ˜์ด์ง€๋ฒˆํ˜ธ/10.0 ) * 10 

    let lastPage = Math.ceil( currentPage/10.0 ) * 10

    //์‹œ์ž‘ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
    const start = lastPage - 9 

    //์ด์ „, ๋‹ค์Œ 
    const prev = start !== 1
    const next = result.value.totalPages > lastPage

    if(result.value.totalPages < lastPage){
      lastPage = result.value.totalPages
    }


    //ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์— ์ถœ๋ ฅ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์—ด๋กœ 
    const pageArr = []

    //์ด์ „
    if(prev){
      pageArr.push({page: start -1, label:'์ด์ „' })
    }

    for(let i = start; i <= lastPage ; i++){
      pageArr.push({page: i, label: i })
    }
    //๋‹ค์Œ
    if(next){
      pageArr.push({page: lastPage + 1, label:'๋‹ค์Œ' })
    }
    return pageArr
  })


  onMounted(() => { // ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ์—ญํ• 

    loadPageData(route.query.page || 1)

  })

  watch(refresh, ()=> { // refresh๊ฐ’์„ ๊ฐ์ง€ํ•˜์—ฌ refresh๋˜๋ฉด loadPageDataํ˜ธ์ถœ

    loadPageData(route.query.page || 1)
  })

  onBeforeRouteUpdate((to, from, next) => { // ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋ณ€๊ฒฝํ• ๋•Œ (ํŽ˜์ด์ง€ ๋ฒˆํ˜ธํด๋ฆญํ•ด์„œ url๋ณ€๊ฒฝ์‹œ)

    loadPageData(to.query.page || 1)
    next()
    
  })

  const moveToRead = (tno) => {
    router.push(`/todo/read/${tno}`)
  }

  return {loading, moveToRead, router, route, refresh, result, pageArr}

}

export default useListData




4. Read

4-1. todoApi

export const getOne = async(mno) => {

    const res = await axios.get(`${host}/${mno}`)
    return res.data
  }




4-2. todoReadComponent.vue

<template>
    <div>
      {{ todo }}
    </div>
  </template>
  
  <script setup>
  import { ref, onMounted } from 'vue';
  import { useRoute } from 'vue-router';
  import { getOne } from '../../apis/todoAPI';
  const route = useRoute()
  const mno = route.params.mno // routers์—์„œ url์ฃผ์†Œ์— mno๊ฐ’์„ ๋„ฃ์€๋’ค์—  
  const todo = ref({
    mno:0,
    title:'',
    writer:'',
    dueDate:''
  })
  onMounted(()=> {
    console.log("mno: " + mno)
    getOne(mno).then(data => todo.value = data) // api์—์„œ ๋ฐ›์€ data๊ฐ’์„ ref๊ฐ’์— ๋Œ€์ž…
  })
  </script>
  
  <style lang="scss" scoped>
  </style>




4-3. todoReadPage.vue

<template>
    <div>
        <h1>Todo Read Page</h1>
        <todoReadComponent></todoReadComponent>
    </div>
</template>

<script setup>
import todoReadComponent from '../../components/todo/todoReadComponent.vue';


</script>

<style lang="scss" scoped>

</style>




5. ADD

5-1. TodoAPI.js

export const postOne = async (obj) => { // ๋“ฑ๋ก

    const res = await axios.post(`${host}`, obj) // post๋Š” ์›๋ž˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ 2๊ฐœ
  
    return res.data
  
}




5-1. TodoAddComponent.vue

<template>
    <div>
      <h1>Todo Add Component</h1>
      <div>
        <input type="text" v-model="todo.title" >
      </div>
      <div>
        <input type="text" v-model="todo.writer" >
      </div>
      <div>
        <input type="date" v-model="todo.dueDate" >
      </div>
      <div>
        <button @click="handleClick" >ADD</button>
      </div>
    </div>
  </template>
  
  <script setup>
  import { ref } from 'vue';
  import { postOne } from '../../apis/todoAPI';
  import { useRouter } from 'vue-router';
  const router = useRouter()
  const todo = ref({ // api์—์„œ์˜ obj๊ฐ’
    title:'',
    writer: '',
    dueDate: ''
  })
  const handleClick = async ()=> {
    console.log(todo.value)
    const res = await postOne(todo.value)
    alert(res.mno)
  }
  </script>
  
  <style lang="scss" scoped>
  </style>




5-2. TodoAddPage.vue

<template>
    <div>
        <h2>Todo Add Page</h2>
        <TodoAddComponent></TodoAddComponent>
    </div>
</template>

<script setup>
import TodoAddComponent from '../../components/todo/TodoAddComponent.vue';
</script>

<style lang="scss" scoped>

</style>




6. DELETE

6-1. todoAPI.js

export const deleteOne = async (mno) => { // ์‚ญ์ œ

    const res = await axios.delete(`${host}/${mno}`)
  
    return res.data

}




6-2. TodoEditComponent.vue


<template>
    <div>
      {{ todo }}
    </div>
    <div>
      <button @click="handleClickDelete">DELETE</button>
    </div>
  </template>
  
  <script setup>
  import { ref, onMounted } from 'vue';
  import { useRoute, useRouter } from 'vue-router';
  import { deleteOne, getOne } from '../../apis/todoAPI';
  import useMember2 from '../../stores/useMember2';
  const route = useRoute()
  const router = useRouter()
  const mno = route.params.mno
  const {computedMid} = useMember2()
  const handleClickDelete = () => {
    deleteOne(mno)
    router.replace("/todo/list")
  }
  const todo = ref({
    mno:0,
    title:'',
    writer:'',
    dueDate:''
  })
  onMounted(()=> {
    console.log("mno: " + mno)
    getOne(mno).then(data => todo.value = data)
  })
  </script>
  
  <style lang="scss" scoped>
  </style>




6-3. TodoEditPage.vue

<template>
    <div>
      <h1>Todo Edit Page</h1>
      <TodoEditComponent></TodoEditComponent>
    </div>
  </template>
  
  <script setup>
  import TodoEditComponent from '../../components/todo/TodoEditComponent.vue';
  </script>
  
  <style lang="scss" scoped>
  </style>




7. Edit

7-1. TodoAPI.js

export const putOne = async (todo) => { // ์ˆ˜์ •

    const res = await axios.put(`${host}/${todo.mno}`, todo)
  
    return res.data
  
}




7-2. TodoEditComponent.vue (์ˆ˜์ • ์ถ”๊ฐ€ ver)

<template>
    <div v-if="error !== null">ERROR {{ error }}</div> <!--์—๋Ÿฌ๊ฐ€ ์žˆ์œผ๋ฉด ์—๋Ÿฌ๋ฉ”์„ธ์ง€ํ‘œ์‹œ -->

<div v-if="error === null">
  <div>
    <input type="text" v-model="todo.mno" readonly> <!--mno, wrtier๋Š” ์ˆ˜์ •๋ถˆ๊ฐ€๋Šฅ-->
  </div>
  <div>
    <input type="text" v-model="todo.title" >
  </div>
  <div>
    <input type="text" v-model="todo.writer" readonly >
  </div>
  <div>
    <input type="date" v-model="todo.dueDate" >
  </div>
  <div>
    <button @click="handleClickModify">MODIFY</button>
    <button @click="handleClickDelete">DELETE</button>
  </div>
</div>
  </template>
  
  <script setup>
  import { ref, onMounted } from 'vue';
  import { useRoute, useRouter } from 'vue-router';
  import { deleteOne, getOne, putOne } from '../../apis/todoAPI';
  import useMember2 from '../../stores/useMember2';
  const route = useRoute()
  const router = useRouter()
  const mno = route.params.mno
  const {computedMid} = useMember2()

  const handleClickModify = () => {
  putOne(todo.value).then(result => { 
    router.replace(`/todo/read/${mno}`) // replace๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ์‚ญ์ œ๋˜๋ฏ€๋กœ ์ˆ˜์ • ๋ฐ ์‚ญ์ œ์—์„œ๋Š” push ๋ณด๋‹จ replace
  })
}

  const handleClickDelete = () => {
    deleteOne(mno)
    router.replace("/todo/list")
  }
  const todo = ref({
    mno:0,
    title:'',
    writer:'',
    dueDate:''
  })

  const error = ref(null)

  onMounted(()=> {
    getOne(mno).then(data => todo.value = data).catch((err) => { // getOne์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋˜๋ฉด then์œผ๋กœ ๊ฐ€์ง€๋งŒ ์‹คํŒจํ•œ๋‹ค๋ฉด catch๋ฌธ
    error.value = err.response.data.message //error์˜ response์†์„ฑ์ค‘ data์†์„ฑ์˜ message
  })
})
  </script>
  
  <style lang="scss" scoped>
  </style>




7-3. TodoEditPage.vue

<template>
    <div>
      <h1>Todo Edit Page</h1>
      <TodoEditComponent></TodoEditComponent>
    </div>
  </template>
  
  <script setup>
  import TodoEditComponent from '../../components/todo/TodoEditComponent.vue';
  </script>
  
  <style lang="scss" scoped>
  </style>

0๊ฐœ์˜ ๋Œ“๊ธ€