iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
JavaScript

歡迎參加我的原生JS畢業典禮系列 第 6

【Day5】Vue的分組報告—實作To Do List(下)

  • 分享至 

  • xImage
  •  

延續昨天,我們已經完成了To Do List的新增與刪除功能。實戰演練中少不了的就是CRUD,今天我們要來完成剩下「修改」與「頁籤搜尋」功能!

修改功能

  • 滑鼠雙擊進入編輯狀態
  • 確認更新與取消的功能鍵
  • 基本防呆:在編輯狀態下不能勾選是否完成任務,避免多次雙擊重複觸發

https://ithelp.ithome.com.tw/upload/images/20240920/20169356YCjA9mKUGQ.png
https://ithelp.ithome.com.tw/upload/images/20240920/201693569h42jmnyAX.png
▲如圖所示,點選當前項目進入編輯畫面,每個項目不會互相影響

Step1.在「新增功能」上,調整塞值放入todos的陣列元素,新增temptextedit

function sendText() {
    if (content.value != "") {
        num.value += 1
        const todo = {
            id: num.value,
            text: content.value,
            temptext: content.value, //加入暫存內容欄位,用來存放編輯狀態下的文字
            status: false,
            edit:false //加入編輯狀態布林值,用來判斷是否在編輯中
        }
        todos.value.push(todo)
        content.value = ""
    }
}

Step2.在Html上,加入一個input,用來當作編輯時的媒介
2-1.v-if要設定todo.edit等於true才會顯示(初始為false)
2-2.v-modeltodo.temptext綁定,每個項目都會跟temptext繫結而不會互相影響

  <input class="todo_input_edit" type="text" v-if="todo.edit" v-model="todo.temptext" />

Step3.新增編輯狀態時確定和取消按鈕,避免和原本元素搞混,使用顏色區分
3-1.該區塊一樣要設定v-if=todo.edit
3-2.在存檔button上加上@click.stop="saveTodo(todo.id)"
3-3.在取消button上加上@click.stop="cancleTodo(todo.id)"

  <span v-if="todo.edit" style="float:right">
      <!--編輯狀態下的button-->
      <button @click.stop="saveTodo(todo.id)" class="btn_icon" type="button" style="color:green;margin-right:5px">&#10004;</button> <!--存檔button-->
      <button @click.stop="cancleTodo(todo.id)" class="btn_icon" type="button" style="color:darkred">&#10008;</button> <!--取消button-->
  </span>

Step4.li標籤加入@dblclick="editTodo(todo)",雙擊觸發編輯狀態

  <li v-for="todo in todos" :key="todo.id" @dblclick="editTodo(todo)">
     ...
  </li>
  function editTodo(todo) {
      if (!todo.edit && !todo.status) //尚未編輯且尚未完成才會觸發
          todos.value = todos.value.map(onetodo => onetodo.id === todo.id ? { ...onetodo, edit: !onetodo.edit } : onetodo)
          //改變edit的狀態為編輯中
  }

Step5.實作確認功能

    function saveTodo(id) {
        todos.value = todos.value.map(todo => todo.id === id ? { ...todo, text: todo.temptext, edit: !todo.edit } : todo)
        //確認存檔,將temptext的值更新到text、edit狀態改為false
    }

Step6.實作取消功能

  function cancleTodo(id) {
      todos.value = todos.value.map(todo => todo.id === id ? { ...todo, temptext:todo.text , edit: !todo.edit } : todo)
      //取消存檔,將text的值更新到temptext、edit狀態改為false
  }

Step7.介面防呆:找到代辦項目前面的☐,加上v-if="!todo.edit",讓它在編輯時不要出現

  <button @click.stop="changeTodo(todo.id)" v-else v-if="!todo.edit" type="button" class="btn_icon" style="float:left">&#9744;</button> <!--未完成button-->

最後成果:

頁籤搜尋

本日的大魔王出現了,沒想到又在這上面卡了好久…前面我們只靠著ref@click事件打天下,一不小心腦袋就打結了~刪刪改改寫了好久,真的不能不請出computed啊!

  • 將頁籤改以響應式資料呈現
  • 新增一個filtertodo陣列去監聽todos計算結果

Step1.把computedimport進來,新增頁籤響應式陣列資料、新增一個變數紀錄當前頁籤位置

import { ref, computed } from 'vue'
const action = ref("all") //紀錄當前頁籤位置,預設為全部
const navitem = ref([
    { name: "全部", value: "all" },
    { name: "進行中", value: "now" },
    { name: "已完成", value: "pass" }
])

Step2.html頁籤改寫,判斷為當前頁籤時給予一個active的class

     <div class="nav_block">
         <button v-for="item in navitem" :class="{'active' : action == item.value}" class="nav_item" @click="action=item.value">{{item.name}}</button>
     </div>

Step3.加入filtertodo,監聽todos計算結果並同時篩選當前頁籤狀態進行過濾

 const filtertodo = computed(function () {
     switch (action.value) {
         case "all": {
             return todos.value.filter(todo => true)
         }
         case "now": {
             return todos.value.filter(todo => !todo.status)
         }
         case "pass": {
             return todos.value.filter(todo => todo.status)
         }
         default: {
             return todos.value
         }
     }
 })

Step?.最後的最後,記得把html中的todos換成filtertodo

    <ul v-if="filtertodo.length > 0" class="todo_item">
        <li v-for="todo in filtertodo" :key="todo.id" @dblclick="editTodo(todo)">
          ...
        </li>
    </ul>
  • 完整html
 <div class="todolist">
     <div class="todo_wrapper">
         <h1 style="border-bottom:1px dotted">To Do List</h1>
         <br>
         <div class="add_block">
             <input v-model="content" class="todo_input" type="text" placeholder="請輸入文字" />
             <button @click="sendText()" class="add_item">加入</button>
         </div>
         <div class="nav_block">
             <button v-for="item in navitem" :class="{'active' : action == item.value}" class="nav_item" @click="action=item.value">{{item.name}}</button>
         </div>
         <ul v-if="filtertodo.length > 0" class="todo_item">
             <li v-for="todo in filtertodo" :key="todo.id" @dblclick="editTodo(todo)">
                 <button @click.stop="changeTodo(todo.id)" v-if="todo.status" class="btn_icon" type="button" style="float:left">&#10004;</button> <!--完成button-->
                 <button @click.stop="changeTodo(todo.id)" v-else v-if="!todo.edit" type="button" class="btn_icon" style="float:left">&#9744;</button> <!--未完成button-->
                 <input class="todo_input_edit" type="text" v-if="todo.edit" v-model="todo.temptext" />
                 <label :class="{ 'line-through': todo.status }" v-else style="margin-left: 5px; cursor: pointer;">{{todo.text}}</label>
                 <span v-if="todo.edit" style="float:right">
                     <!--編輯狀態下的button-->
                     <button @click.stop="saveTodo(todo.id)" class="btn_icon" type="button" style="color:green;margin-right:5px">&#10004;</button> <!--存檔button-->
                     <button @click.stop="cancleTodo(todo.id)" class="btn_icon" type="button" style="color:darkred">&#10008;</button> <!--取消button-->
                 </span>
                 <button @click.stop="deleteTodo(todo.id)" v-else class="btn_icon" type="button" style="float:right">&#10008;</button> <!--刪除button-->
             </li>
         </ul>
         <ul class="todo_item" v-else>
             <li>沒有資料</li>
         </ul>
     </div>
 </div>
  • 完整css
   li {
       width: 100%;
       height: 50px;
       list-style: none;
       background-color: #ffffff;
       padding: 13px;
       cursor: pointer;
   }
   li:hover {
       background-color: #cacaca;
   }
   .todolist {
       min-height: 100vh;
       display: flex;
       align-items: center;
   }
   .todo_wrapper {
       display: flex;
       flex-direction: column;
       justify-content: center;
       align-items:center;
       background-color: #ededed;
       height:500px;
       width:500px;
       border-radius:5px;
   }
   .add_block {
       display: inline-flex;
       width: 85%;
       justify-content: space-around
   }
   .todo_input {
       border: none;
       outline-style: none;
       padding: .375rem .75rem;
       width: 85%;
   }
   .todo_input_edit {
       border: 1px solid #cacaca;
       border-radius:5px;
       outline-style: none;
       padding: .375rem .75rem;
       width: 85%;
       margin-top:-5px;
   }
   .add_item {
       border: none;
       background-color: #808080;
       margin-left: 10px;
       color: aliceblue;
       cursor: pointer;
       padding: .375rem .75rem;
   }
   .nav_block {
       margin-top: 15px;
       border-bottom: 1px solid #808080;
       width: 100%
   }
   .nav_item {
       border: none;
       background-color: #ffffff;
       margin-left: 10px;
       color:#808080;
       cursor: pointer;
       padding: .375rem .75rem
   }
   .todo_item {
       width: 85%;
       height: 300px;
       overflow-y: auto;
       margin-top: 20px;
       scrollbar-width: thin;
       padding: inherit;
   }
   .btn_icon {
       background: none;
       border: none;
       cursor: pointer;
   }
   .line-through {
       text-decoration:line-through
   }
   .active {
       background-color: #808080;
       color: white
   }
  • 完整js
 import { ref, computed } from 'vue'
 const num = ref(0) //作為todolist中的id計數
 const content = ref('') //和v-model進行介面input輸入框的資料雙向綁定
 const todos = ref([])
 const action = ref("all")
 const navitem = ref([
     { name: "全部", value: "all" },
     { name: "進行中", value: "now" },
     { name: "已完成", value: "pass" }
 ])
 const filtertodo = computed(function () {
     switch (action.value) {
         case "all": {
             return todos.value.filter(todo => true)
         }
         case "now": {
             return todos.value.filter(todo => !todo.status)
         }
         case "pass": {
             return todos.value.filter(todo => todo.status)
         }
         default: {
             return todos.value
         }
     }
 })
 function sendText() {
     if (content.value != "") {
         num.value += 1
         const todo = {
             id: num.value,
             text: content.value,
             temptext: content.value,
             status: false,
             edit:false
         }
         todos.value.push(todo)
         content.value = ""
     }
 }
 function deleteTodo(id) {
     todos.value = todos.value.filter(todo => todo.id !== id)
 }
 function changeTodo(id) {
     todos.value = todos.value.map(todo => todo.id === id ? { ...todo, status: !todo.status } : todo)
 }
 function editTodo(todo) {
     if (!todo.edit && !todo.status) //尚未編輯且尚未完成才會觸發
         todos.value = todos.value.map(onetodo => onetodo.id === todo.id ? { ...onetodo, edit: !onetodo.edit } : onetodo)
 }
 function cancleTodo(id) {
     todos.value = todos.value.map(todo => todo.id === id ? { ...todo, temptext:todo.text , edit: !todo.edit } : todo)
 }
 function saveTodo(id) {
     todos.value = todos.value.map(todo => todo.id === id ? { ...todo, text: todo.temptext, edit: !todo.edit } : todo)
 }

後記

一卡關就卡了好幾小時甚至快整天時間…(還想著每天都要超前打一些明天的內容,真是辦不到:),看來離熟練還有很長一段路要走(淚目)但還是要加油!
P.S.雖然一度又快趕不出來今天的進度,但中途還是去看了今天上映的我的英雄學院:You're Next劇場版


參考資料
竹白筆記本—計算 & 監聽
Vue.js - 實例-ToDoList


上一篇
【Day4】Vue的分組報告—實作To Do List(上)
下一篇
【Day6】輔修CSS預處理器—了解Sass/SCSS
系列文
歡迎參加我的原生JS畢業典禮31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言