vue_todo案例超详细讲解(可跟做练手项目)
文章目錄
- 一、🧑?💻todo案例
- 🔥1.0_todo案例-創建工程和組件
- 🔥1.1_todo案例-鋪設待辦任務
- 🔥1.2_todo案例-添加任務
- 🔥1.3_todo案例-刪除任務
- 🔥1.4_todo案例-底部統計
- 🔥1.5_todo案例-數據切換
- 🔥1.6_todo案例-清空已完成
- 🔥1.7_todo案例-數據緩存
- 🔥1.8_todo案例-全選功能
- 🔥==base.css==
- 🔥==index.css==
一、🧑?💻todo案例
🔥1.0_todo案例-創建工程和組件
目標: 新建工程, 準備好所需的一切
- 需求1: 創建新工程
- 需求2: 分組件創建 – 準備標簽和樣式
分析:
? ①:初始化todo工程
? ②:創建3個組件和里面代碼(在預習資料.md復制)
? ③:把styles的樣式文件準備好(從預習資料復制)
? ④: App.vue引入注冊使用, 最外層容器類名todoapp
預先準備: 把styles的樣式文件準備好 在App.vue引入使用(文件樣式在最底下)
// 1.0 樣式引入 import "./styles/base.css" import "./styles/index.css"根據需求: 我們定義3個組件準備復用
components/TodoHeader.vue - 復制標簽和類名
<template><header class="header"><h1>todos</h1><input id="toggle-all" class="toggle-all" type="checkbox" ><label for="toggle-all"></label><inputclass="new-todo"placeholder="輸入任務名稱-回車確認"autofocus/></header> </template><script> export default {} </script>components/TodoMain.vue - 復制標簽和類名
<template><ul class="todo-list"><!-- completed: 完成的類名 --><li class="completed" ><div class="view"><input class="toggle" type="checkbox" /><label>任務名</label><button class="destroy"></button></div></li></ul></template><script> export default { } </script>components/TodoFooter.vue - 復制標簽和類名
<template><footer class="footer"><span class="todo-count">剩余<strong>數量值</strong></span><ul class="filters"><li><a class="selected" href="javascript:;" >全部</a></li><li><a href="javascript:;">未完成</a></li><li><a href="javascript:;" >已完成</a></li></ul><button class="clear-completed" >清除已完成</button></footer> </template><script> export default {} </script>App.vue中引入和使用
<template><section class="todoapp"><!-- 除了駝峰, 還可以使用-轉換鏈接 --><TodoHeader></TodoHeader><TodoMain></TodoMain><TodoFooter></TodoFooter></section> </template><script> // 1.0 樣式引入 import "./styles/base.css" import "./styles/index.css"import TodoHeader from "./components/TodoHeader"; import TodoMain from "./components/TodoMain"; import TodoFooter from "./components/TodoFooter";export default {components: {TodoHeader,TodoMain,TodoFooter,}, }; </script>🔥1.1_todo案例-鋪設待辦任務
目的: 把待辦任務, 展示到頁面TodoMain.vue組件上
- 需求1: 把待辦任務, 展示到頁面TodoMain.vue組件上
- 需求2: 關聯選中狀態, 設置相關樣式
分析:
? ①: App.vue – 準備數組傳入TodoMain.vue內
? ②: v-for循環展示數據
? ③: v-model綁定復選框選中狀態
? ④: 根據選中狀態, 設置完成劃線樣式
App.vue
<TodoMain :arr="showArr"></TodoMain>export default {data() {return {list: [{ id: 100, name: "吃飯", isDone: true },{ id: 201, name: "睡覺", isDone: false },{ id: 103, name: "打豆豆", isDone: true },],};} };TodoMain.vue
<template><ul class="todo-list"><!-- 2.2 循環任務-關聯選中狀態-鋪設數據 --><!-- completed: 完成的類名 --><li :class="{completed: obj.isDone}" v-for="(obj, index) in arr" :key='obj.id'><div class="view"><input class="toggle" type="checkbox" v-model="obj.isDone"/><label>{{ obj.name }}</label><!-- 4.0 注冊點擊事件 --><button @click="delFn(index)" class="destroy"></button></div></li></ul> </template><script> export default {props: ["list"] }; </script><style> </style>🔥1.2_todo案例-添加任務
目標: 在頂部輸入框輸入要完成的任務名, 敲擊回車, 完成新增功能
- 需求: 輸入任務敲擊回車, 新增待辦任務
分析:
? ①: TodoHeader.vue – 輸入框 – 鍵盤事件 – 回車按鍵
? ②: 子傳父, 把待辦任務 – App.vue中 – 加入數組list里
? ③: 原數組改變, 所有用到的地方都會更新
? ④: 輸入框為空, 提示用戶必須輸入內容
TodoHeader.vue
<template><header class="header"><h1>todos</h1><input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"><label for="toggle-all"></label><!-- 3.0 鍵盤事件-回車按鍵3.1 輸入框 - v-model獲取值--><inputclass="new-todo"placeholder="輸入任務名稱-回車確認"autofocus@keydown.enter="downFn"v-model="task"/></header> </template><script> // 3. 目標 - 新增任務 export default {data(){return {task: ""}},methods: {downFn(){if (this.task.trim().length === 0) {alert("任務名不能為空");return;}// 3.2(重要) - 當前任務名字要加到list數組里// 子傳父技術this.$emit("create", this.task)this.task = ""}} } </script>App.vue
<TodoHeader @create="createFn"></TodoHeader>methods: {createFn(taskName){ // 添加任務// 3.3 push到數組里let id = this.list.length == 0 ? 100 : this.list[this.list.length - 1].id + 1this.list.push({id: id,name: taskName,isDone: false})}, }🔥1.3_todo案例-刪除任務
目標: 實現點x, 刪除任務功能
- 需求: 點擊任務后的x, 刪除當前這條任務
分析:
? ①: x標簽 – 點擊事件 – 傳入id區分
? ②: 子傳父, 把id傳回– App.vue中 – 刪除數組list里某個對應的對象
? ③: 原數組改變, 所有用到的地方都會更新
App.vue - 傳入自定義事件等待接收要被刪除的序號
<TodoMain :arr="showArr" @del="deleteFn"></TodoMain>methods: {deleteFn(theId){ // 刪除任務let index = this.list.findIndex(obj => obj.id === theId)this.list.splice(index, 1)}, },TodoMain.vue - 把id傳回去實現刪除(想好數據在哪里, 就在哪里刪除)
<!-- 4.0 注冊點擊事件 --> <button class="destroy" @click="delFn(obj.id)"></button>methods: {delFn(id){// 4.1 子傳父this.$emit('del', id)} }🔥1.4_todo案例-底部統計
目的: 顯示現在任務的總數
- 需求: 統計當前任務的條數
分析:
? ①: App.vue中 – 數組list – 傳給TodoFooter.vue
? ②: 直接在標簽上顯示 / 定義計算屬性用于顯示都可以
? ③: 原數組只要改變, 所有用到此數組的地方都會更新
TodoFooter.vue - 接收list統計直接顯示
<template><footer class="footer"><span class="todo-count">剩余<strong>{{ count }}</strong></span><ul class="filters"><li><a class="selected" href="javascript:;">全部</a></li><li><a href="javascript:;">未完成</a></li><li><a href="javascript:;">已完成</a></li></ul><button class="clear-completed">清除已完成</button></footer> </template><script> export default {// 5.0 props定義props: ['farr'],// 5.1 計算屬性 - 任務數量computed: {count(){return this.farr.length}}, } </script><style></style>App.vue - 傳入數據
<TodoFooter :farr="showArr"></TodoFooter>🔥1.5_todo案例-數據切換
目的: 點擊底部切換數據
- 需求1: 點擊底部切換 – 點誰誰有邊框
- 需求2: 對應切換不同數據顯示
分析:
? ①: TodoFooter.vue – 定義isSel – 值為all, yes, no其中一種
? ②: 多個class分別判斷誰應該有類名selected
? ③: 點擊修改isSel的值
? ④: 子傳父, 把類型isSel傳到App.vue
? ⑤: 定義計算屬性showArr, 決定從list里顯示哪些數據給TodoMain.vue和TodoFooter.vue
App.vue
<TodoFooter :farr="showArr" @changeType="typeFn"></TodoFooter><script>export default{data(){return {// ...其他省略getSel: "all" // 默認顯示全部}},methods: {// ...其他省略typeFn(str){ // 'all' 'yes' 'no' // 修改類型this.getSel = str},},// 6.5 定義showArr數組 - 通過list配合條件篩選而來computed: {showArr(){if (this.getSel === 'yes') { // 顯示已完成return this.list.filter(obj => obj.isDone === true)} else if (this.getSel === 'no') { // 顯示未完成return this.list.filter(obj => obj.isDone === false)} else {return this.list // 全部顯示}}},} </script>TodoFooter.vue
<template><footer class="footer"><span class="todo-count">剩余<strong>{{ count }}</strong></span><ul class="filters" @click="fn"><li><!-- 6.1 判斷誰應該有高亮樣式: 動態class6.2 用戶點擊要切換isSel里保存的值--><a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">全部</a></li><li><a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">未完成</a></li><li><a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">已完成</a></li></ul><!-- 7. 目標: 清除已完成 --><!-- 7.0 點擊事件 --><button class="clear-completed" >清除已完成</button></footer> </template><script> // 5. 目標: 數量統計 export default {// 5.0 props定義props: ['farr'],// 5.1 計算屬性 - 任務數量computed: {count(){return this.farr.length}},// 6. 目標: 點誰誰亮// 6.0 變量isSeldata(){return {isSel: 'all' // 全部:'all', 已完成'yes', 未完成'no'}},methods: {fn(){ // 切換篩選條件// 6.3 子 -> 父 把類型字符串傳給App.vue this.$emit("changeType", this.isSel)}} } </script>🔥1.6_todo案例-清空已完成
目的: 點擊右下角按鈕- 把已經完成的任務清空了
- 需求: 點擊右下角鏈接標簽, 清除已完成任務
分析:
? ①: 清空標簽 – 點擊事件
? ②: 子傳父 – App.vue – 一個清空方法
? ③: 過濾未完成的覆蓋list數組 (不考慮恢復)
App.vue - 先傳入一個自定義事件-因為得接收TodoFooter.vue里的點擊事件
<TodoFooter :farr="showArr" @changeType="typeFn" @clear="clearFun"></TodoFooter><script>methods: {// ...省略其他clearFun(){ // 清除已完成this.list = this.list.filter(obj => obj.isDone == false)}} </script>TodoFooter.vue
<!-- 7. 目標: 清除已完成 --> <!-- 7.0 點擊事件 --> <button class="clear-completed" @click="clearFn">清除已完成</button><script>methods: {clearFn(){ // 清空已完成任務// 7.1 觸發App.vue里事件對應clearFun方法this.$emit('clear')}} </script>🔥1.7_todo案例-數據緩存
目的: 新增/修改狀態/刪除 后, 馬上把數據同步到瀏覽器本地存儲
- 需求: 無論如何變化 – 都保證刷新后數據還在
分析:
? ①: App.vue – 偵聽list數組改變 – 深度
? ②: 覆蓋式存入到本地 – 注意本地只能存入JSON字符串
? ③: 刷新頁面 – list應該默認從本地取值 – 要考慮無數據情況空數組
App.vue
<script>export default {data(){return {// 8.1 默認從本地取值list: JSON.parse(localStorage.getItem('todoList')) || [],// 6.4 先中轉接收類型字符串getSel: "all" // 默認顯示全部}},// 8. 目標: 數據緩存watch: {list: {deep: true,handler(){// 8.0 只要list變化 - 覆蓋式保存到localStorage里localStorage.setItem('todoList', JSON.stringify(this.list))}}}}; </script>🔥1.8_todo案例-全選功能
目標: 點擊左上角v號, 可以設置一鍵完成, 再點一次取消全選
- 需求1: 點擊全選 – 小選框受到影響
- 需求2: 小選框都選中(手選) – 全選自動選中狀態
分析:
? ①: TodoHeader.vue – 計算屬性 - isAll
? ②: App.vue – 傳入數組list – 在isAll的set里影響小選框
? ③: isAll的get里統計小選框最后狀態, 影響isAll – 影響全選狀態
? ④: 考慮無數據情況空數組 – 全選不應該勾選
提示: 就是遍歷所有的對象, 修改他們的完成狀態屬性的值
TodoHeader.vue
<!-- 9. 目標: 全選狀態 9.0 v-model關聯全選狀態 頁面變化(勾選true, 未勾選false) -> v-model -> isAll變量 --> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"><script>export default {// ...其他省略// 9.1 定義計算屬性computed: {isAll: {set(checked){ // 只有true / false// 9.3 影響數組里每個小選框綁定的isDone屬性this.arr.forEach(obj => obj.isDone = checked)},get(){// 9.4 小選框統計狀態 -> 全選框// 9.5 如果沒有數據, 直接返回false-不要讓全選勾選狀態return this.arr.length !== 0 && this.arr.every(obj => obj.isDone === true)}}},} </script>App.vue
<TodoHeader :arr="list" @create="createFn"></TodoHeader>🔥base.css
hr {margin: 20px 0;border: 0;border-top: 1px dashed #c5c5c5;border-bottom: 1px dashed #f7f7f7; }.learn a {font-weight: normal;text-decoration: none;color: #b83f45; }.learn a:hover {text-decoration: underline;color: #787e7e; }.learn h3, .learn h4, .learn h5 {margin: 10px 0;font-weight: 500;line-height: 1.2;color: #000; }.learn h3 {font-size: 24px; }.learn h4 {font-size: 18px; }.learn h5 {margin-bottom: 0;font-size: 14px; }.learn ul {padding: 0;margin: 0 0 30px 25px; }.learn li {line-height: 20px; }.learn p {font-size: 15px;font-weight: 300;line-height: 1.3;margin-top: 0;margin-bottom: 0; }#issue-count {display: none; }.quote {border: none;margin: 20px 0 60px 0; }.quote p {font-style: italic; }.quote p:before {content: '“';font-size: 50px;opacity: .15;position: absolute;top: -20px;left: 3px; }.quote p:after {content: '”';font-size: 50px;opacity: .15;position: absolute;bottom: -42px;right: 3px; }.quote footer {position: absolute;bottom: -40px;right: 0; }.quote footer img {border-radius: 3px; }.quote footer a {margin-left: 5px;vertical-align: middle; }.speech-bubble {position: relative;padding: 10px;background: rgba(0, 0, 0, .04);border-radius: 5px; }.speech-bubble:after {content: '';position: absolute;top: 100%;right: 30px;border: 13px solid transparent;border-top-color: rgba(0, 0, 0, .04); }.learn-bar > .learn {position: absolute;width: 272px;top: 8px;left: -300px;padding: 10px;border-radius: 5px;background-color: rgba(255, 255, 255, .6);transition-property: left;transition-duration: 500ms; }@media (min-width: 899px) {.learn-bar {width: auto;padding-left: 300px;}.learn-bar > .learn {left: 8px;} }🔥index.css
html, body {margin: 0;padding: 0; }button {margin: 0;padding: 0;border: 0;background: none;font-size: 100%;vertical-align: baseline;font-family: inherit;font-weight: inherit;color: inherit;-webkit-appearance: none;appearance: none;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale; }body {font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;line-height: 1.4em;background: #f5f5f5;color: #4d4d4d;min-width: 230px;max-width: 550px;margin: 0 auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;font-weight: 300; }:focus {outline: 0; }.hidden {display: none; }.todoapp {background: #fff;margin: 130px 0 40px 0;position: relative;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),0 25px 50px 0 rgba(0, 0, 0, 0.1); }.todoapp input::-webkit-input-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6; }.todoapp input::-moz-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6; }.todoapp input::input-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6; }.todoapp h1 {position: absolute;top: -155px;width: 100%;font-size: 100px;font-weight: 100;text-align: center;color: rgba(175, 47, 47, 0.15);-webkit-text-rendering: optimizeLegibility;-moz-text-rendering: optimizeLegibility;text-rendering: optimizeLegibility; }.new-todo, .edit {position: relative;margin: 0;width: 100%;font-size: 24px;font-family: inherit;font-weight: inherit;line-height: 1.4em;border: 0;color: inherit;padding: 6px;border: 1px solid #999;box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);box-sizing: border-box;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale; }.new-todo {padding: 16px 16px 16px 60px;border: none;background: rgba(0, 0, 0, 0.003);box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); }.main {position: relative;z-index: 2;border-top: 1px solid #e6e6e6; }.toggle-all {text-align: center;border: none; /* Mobile Safari */opacity: 0;position: absolute; }.toggle-all + label {width: 60px;height: 34px;font-size: 0;position: absolute;top: -52px;left: -13px;-webkit-transform: rotate(90deg);transform: rotate(90deg); }.toggle-all + label:before {content: '?';font-size: 22px;color: #e6e6e6;padding: 10px 27px 10px 27px; }.toggle-all:checked + label:before {color: #737373; }.todo-list {margin: 0;padding: 0;list-style: none; }.todo-list li {position: relative;font-size: 24px;border-bottom: 1px solid #ededed; }.todo-list li:last-child {border-bottom: none; }.todo-list li.editing {border-bottom: none;padding: 0; }.todo-list li.editing .edit {display: block;width: 506px;padding: 12px 16px;margin: 0 0 0 43px; }.todo-list li.editing .view {display: none; }.todo-list li .toggle {text-align: center;width: 40px;/* auto, since non-WebKit browsers doesn't support input styling */height: auto;position: absolute;top: 0;bottom: 0;margin: auto 0;border: none; /* Mobile Safari */-webkit-appearance: none;appearance: none; }.todo-list li .toggle {opacity: 0; }.todo-list li .toggle + label {/*Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/*/background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');background-repeat: no-repeat;background-position: center left; }.todo-list li .toggle:checked + label {background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); }.todo-list li label {word-break: break-all;padding: 15px 15px 15px 60px;display: block;line-height: 1.2;transition: color 0.4s; }.todo-list li.completed label {color: #d9d9d9;text-decoration: line-through; }.todo-list li .destroy {display: none;position: absolute;top: 0;right: 10px;bottom: 0;width: 40px;height: 40px;margin: auto 0;font-size: 30px;color: #cc9a9a;margin-bottom: 11px;transition: color 0.2s ease-out; }.todo-list li .destroy:hover {color: #af5b5e; }.todo-list li .destroy:after {content: '×'; }.todo-list li:hover .destroy {display: block; }.todo-list li .edit {display: none; }.todo-list li.editing:last-child {margin-bottom: -1px; }.footer {color: #777;padding: 10px 15px;height: 20px;text-align: center;border-top: 1px solid #e6e6e6; }.footer:before {content: '';position: absolute;right: 0;bottom: 0;left: 0;height: 50px;overflow: hidden;box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0, 0, 0, 0.2),0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0, 0, 0, 0.2); }.todo-count {float: left;text-align: left; }.todo-count strong {font-weight: 300; }.filters {margin: 0;padding: 0;list-style: none;position: absolute;right: 0;left: 0; }.filters li {display: inline; }.filters li a {color: inherit;margin: 3px;padding: 3px 7px;text-decoration: none;border: 1px solid transparent;border-radius: 3px; }.filters li a:hover {border-color: rgba(175, 47, 47, 0.1); }.filters li a.selected {border-color: rgba(175, 47, 47, 0.2); }.clear-completed, html .clear-completed:active {float: right;position: relative;line-height: 20px;text-decoration: none;cursor: pointer; }.clear-completed:hover {text-decoration: underline; }.info {margin: 65px auto 0;color: #bfbfbf;font-size: 10px;text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);text-align: center; }.info p {line-height: 1; }.info a {color: inherit;text-decoration: none;font-weight: 400; }.info a:hover {text-decoration: underline; }/*Hack to remove background from Mobile Safari.Can't use it globally since it destroys checkboxes in Firefox */ @media screen and (-webkit-min-device-pixel-ratio:0) {.toggle-all,.todo-list li .toggle {background: none;}.todo-list li .toggle {height: 40px;} }@media (max-width: 430px) {.footer {height: 50px;}.filters {bottom: 10px;} }總結
以上是生活随笔為你收集整理的vue_todo案例超详细讲解(可跟做练手项目)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【202209秋招软开银行面试C++】
- 下一篇: C语言 投票系统