Vue官網todoMVC示例

hunger-Jack發表於2018-01-30

這個示例是模仿官網示例樣式和功能用我自己的方式寫的,基本上沒有看官網的原始碼,只參考自定義指令。讓我們一步步來探討一下。官網demo

要實現的功能

  1. 單條新增todo
  2. 單條刪除todo
  3. 雙擊編輯todo
  4. 單條todo已完成相應樣式狀態改變
  5. 全部todo是已完成相應樣式狀態改變
  6. 清除全部已完成todos
  7. 待辦todos數量顯示
  8. 所有todos,已完成todos,未完成todos篩選

todoMVC.gif

單條新增todo

<input type="text" class="todos_add" placeholder="What needs to be done?" 
@keyup.enter="addTodo($event.target)" //繫結enter事件
ref="currentInput">//操作input元素使enter一下之後清空輸入框內容
複製程式碼
data() {//一些初始化資料
	return {
		todolists: [],
		dataStatus: ["All", "Active", "Completed"],
		dataStatusIndex: 0,
		whichShow: true,
		defaultShow: true
	}
},
複製程式碼
addTodo(e) { //新增todo
	var val = e.value
	if (val === "") {return} //如果輸入內容為空則立即返回
	this.todoLists = this.todoLists.concat({//使用concat這個api新增todo
		value: val, //輸入內容
		isEditing: false, //是否在編輯狀態
		isActive: false, //刪除X圖示是否啟用
		isChecked: false //是否已完成
	})
	this.$refs.currentInput.value = "" //按下enter新增todo之後把輸入框value清零
	window.localStorage.setItem("content",JSON.stringify(this.todoLists))//使用localStorage以JSON格式儲存資料
},
複製程式碼

把每條todo的對應狀態都存在同一個物件當中,在操作改變todo狀態的時候不會被統一處理,使得每條todo都有單獨的狀態。

單條刪除todo

 <li  class="content_todoList"
 v-for="(list,index) in todoLists" 
 @mouseover="list.isActive = true" //滑鼠移入todo改變對應todo的isActive狀態
 @mouseleave="list.isActive=false" //滑鼠移出todo改變對應todo的isActive狀態
	<span class="el-icon-close content_todoList_delete" 
	:class="{show: list.isActive}" //動態繫結class使滑鼠移動到某一todo顯示X圖示
	@click="deleteTodo(index)"> //繫結刪除單條todo事件
	</span>
</li>
複製程式碼
deleteTodo(index) { //刪除單條todo
		this.todoLists.splice(index, 1)//使用splice的api
		window.localStorage.setItem("content",JSON.stringify(todoLists)) //以JSON格式儲存資料//localStorage儲存資料
},
複製程式碼

在每個li元素上繫結了滑鼠移入和移除的事件來動態的改變每個todo的isActive,然後再使用isActive動態顯示class。

雙擊編輯todo&&單條todo已完成

<input type="checkbox" class="checkBox" 
v-model="list.isChecked">//雙向繫結isChecked

<div class="content_todoList_main" 
@dblclick="toEdit(list)" //雙擊事件
v-show="!list.isEditing" //切換元素
:class="{deleted:list.isChecked}"> //動態繫結class該表已完成todo樣式
{{list.value}}
</div>

<input type="text" class="content_todoList_main main_input" 
v-model="list.value" //雙向繫結可輸入value
v-show="list.isEditing" //切換元素
v-todo-focus="list.value" //自定義指令,雙擊之後自動focus對焦
@blur="unEdit(list)"> //繫結blur失去焦點事件
複製程式碼
methods: {
	toEdit(obj) { //使新增的todothing可編輯
		obj.isEditing = true
	},
	
	unEdit(obj) { //使新增的todothing不可編輯
		obj.isEditing = false
	},
}

directives: { //自定義focus指令,需要一個binding引數做判斷
	"todo-focus": function (el, binding) {
		if (binding.value) {
			el.focus()
		}
	}
}
複製程式碼

通過每個todo裡的isEditing屬性控制show和是否可編輯兩個狀態,雙擊div之後改變當前todo的isEditing為true,從而顯示為input,input失去焦點之後再通過blur事件改為false。 通過todo的idChecked來控制是否已完成,從而動態改變樣式。

全部todos已完成

<span 
class="icon-down el-icon-arrow-down" //使用element庫做向下箭頭icon
v-show="todoLists.length>0" //通過todoLists控制是否顯示向下箭頭icon
@click="selectAllTodos"></span> //全部已完成事件
複製程式碼
selectAllTodos() { //設定所有todo為已完成,使用map的api通過todo的isChecked屬性操作
	this.todoLists.map(todo => todo.isChecked = todo.isChecked ? false : true)
}
複製程式碼

待辦todos數量顯示

<div class="data_times" v-show="times === 0"> //times為0顯示item,大於0顯示items,細節註定成敗
	<span>{{times}}</span>&nbsp item left
</div>
<div class="data_times" v-show="times > 0">
<span>{{times}}</span>&nbsp items left</div>
複製程式碼
computed: {
	times() { //使用計算屬性計算待辦todos的次數 
		let todoArr = this.todoLists
		let times = 0
		for (let i = 0; i < todoArr.length; i++) {
			if (todoArr[i].isChecked === false) {
				times++
			}
		}
		return times
	}
},
複製程式碼

使用了計算屬性對todoLists計算,用for迴圈刷選出idChecked為true的累加,最後返回times。

清除全部已完成todos

<div class="data_clearTodos" 
@click="clearTodos" 
v-show="times < todoLists.length"> //如果待辦事件次數小於總todoLists長度就顯示“clear completed”
	<a href="#">clear completed</a>
</div>

<div class="data_clearTodos" 
@click="clearTodos" 
v-show="times === todoLists.length"> //如果待辦事件次數等於總todoLists長度就顯示“clear completed”
	<a href="#"></a>
</div>
複製程式碼
clearTodos() { //清空已完成的todoLists,使用filter的api進行篩選
	this.todoLists = this.todoLists.filter(todo => todo.isChecked === false)
	window.localStorage.setItem("content",JSON.stringify(this.todoLists)) //以json格式儲存資料
},
複製程式碼

如果待辦todos的times小於todoLists長度,就證明有已完成的todo,就可以顯示“clear completed”,如果相等就說明沒有已完成的todo。

三種狀態篩選

所有todos,已完成todos,未完成todos篩選

<li class="content_todoList" 
v-show="defaultShow || (whichShow?list.isChecked:!list.isChecked)">

<div class="data_status">
	<a href="#" 
	:class="{active: index === dataStatusIndex}" //動態class實現tab切換
	v-for="(item ,index) in dataStatus" v-for迴圈
	@click="switchStatus(index)" //切換不同tab顯示內容
	:key="index">
		{{item}}
	</a>
</div>
複製程式碼
switchStatus(index) { //通過if條件判斷操作
	this.dataStatusIndex = index
	if (this.dataStatus[index] === "Active") {
		this.defaultShow = false
		this.whichShow = false
	} else if (this.dataStatus[index] === "Completed") {
		this.defaultShow = false
		this.whichShow = true
	} else if (this.dataStatus[index] === "All") {
		this.defaultShow = true
	}
},
複製程式碼

我這裡是同時if條件句判斷操作,有點麻煩,目前還沒有想出來其他方案。在li元素使用三元運算子和或運算子進行操作顯示不同狀態的todos。

完整程式碼

<style>
	* {
		padding: 0;
		margin: 0;
		box-sizing: border-box;
	}

	input {
		outline: none;
	}

	ul,
	li,
	ol {
		list-style: none;
	}

	#app {
		width: 800px;
		height: 900px;
		margin: 0 auto;
		text-align: center;
		background-color: rgb(245, 245, 245);
		padding: 24px 0;
	}

	#app .header {
		font-size: 48px;
	}

	.content {
		width: 72%;
		margin: 0 auto;
		box-shadow: 0 3px 3px 2px rgba(0, 0, 0, 0.25);
		position: relative;
	}

	.icon-down {
		position: absolute;
		font-size: 24px;
		top: 16px;
		left: 16px;
		cursor: pointer;
	}

	#app .content .todos_add {
		width: 100%;
		height: 56px;
		padding: 16px 56px;
		font-size: 24px;
		border: 1px solid transparent;
	}

	.content_todoLists {
		position: relative;
		z-index: 3;
	}

	.content_todoList {
		display: flex;
		flex-direction: row;
		border-top: 1px solid #ccc;
		font-size: 24px;
		padding: 8px;
		background-color: white;
		align-items: center;
	}

	.checkBox {
		width: 20px;
		height: 20px;
		margin-left: 10px;
	}

	.content_todoList_main {
		flex: 1;
		text-align: left;
		margin-left: 16px;
		font-size: 20px;
		padding: 6px 0;
	}

	.main_input {
		position: relative;
		z-index: 1;
	}

	.content_todoList_delete {
		position: absolute;
		right: 16px;
		color: rgb(252, 55, 55);
		font-weight: 500;
		display: none;
		cursor: pointer;
	}

	.show {
		display: block;
	}

	.deleted {
		text-decoration-line: line-through;
		color: #bbb;
	}

	.show:hover {
		color: rgb(255, 0, 0);
		font-weight: 700;
	}

	::-moz-placeholder {
		color: rgb(221, 218, 218);
	}

	::-webkit-input-placeholder {
		color: rgb(221, 218, 218);
	}

	:-ms-input-placeholder {
		color: rgb(221, 218, 218);
	}

	.data {
		display: flex;
		justify-content: space-between;
		padding: 8px;
		font-size: 14px;
		font-weight: 300;
		color: rgb(145, 145, 145);
	}

	a {
		text-decoration: none;
		color: rgb(145, 145, 145);
	}

	.data_times {
		width: 73px;
	}

	.data_clearTodos {
		width: 142px;
	}

	.data_status a {
		display: inline-block;
		border: 1px solid transparent;
		border-radius: 2px;
		padding: 1px 4px;
		margin: 0 2px;
	}

	.data_status a:hover {
		border-color: #bbb;
	}

	.data_clearTodos a:hover {
		text-decoration-line: underline;
	}

	.active {
		box-shadow: 0 0 1px black;
	}
</style>
複製程式碼
<body>
	<div id="app">
		<header class="header">todos</header>
		<div class="content">
			<span class="icon-down el-icon-arrow-down" 
			v-show="todoLists.length>0" 
			@click="selectAllTodos">
			</span>
			<input type="text" class="todos_add" placeholder="What needs to be done?" 
			@keyup.enter="addTodo($event.target)" 
			ref="currentInput">
			<ul class="content_todoLists">
				<li v-for="(list,index) in todoLists" class="content_todoList" 
				@mouseover="list.isActive = true" 
				@mouseleave="list.isActive=false"
				v-show="defaultShow || (whichShow?list.isChecked:!list.isChecked)">
					<input type="checkbox" class="checkBox" v-model="list.isChecked">
					<div class="content_todoList_main" @dblclick="toEdit(list)" v-show="!list.isEditing" :class="{deleted:list.isChecked}">
						{{list.value}}
					</div>
					<input type="text" class="content_todoList_main main_input" 
					v-model="list.value" 
					v-show="list.isEditing" 
					v-todo-focus="list.value"
					@blur="unEdit(list)">
					<span class="el-icon-close content_todoList_delete" :class="{show: list.isActive}" @click="deleteTodo(index)"></span>
				</li>
			</ul>
			<div class="data" v-show="todoLists.length>0">
				<div class="data_times" v-show="times === 0">
					<span>{{times}}</span>&nbspitem left
				</div>
				<div class="data_times" v-show="times > 0">
					<span>{{times}}</span>&nbspitems left
				</div>
				<div class="data_status">
					<a href="#" :class="{active:index === dataStatusIndex}" v-for="(item,index) in dataStatus" @click="switchStatus(index)" :key="index">
						{{item}}
					</a>
				</div>
				<div class="data_clearTodos" @click="clearTodos" v-show="times < todoLists.length">
					<a href="#">clear completed</a>
				</div>
				<div class="data_clearTodos" @click="clearTodos" v-show="times === todoLists.length">
					<a href="#"></a>
				</div>
			</div>
		</div>
	</div>
</body>
複製程式碼
<script>
	let vm = new Vue({
		el: "#app",
		data() {
			return {
				todoLists: [],
				dataStatus: ["All", "Active", "Completed"],
				dataStatusIndex: 0,
				whichShow: true,
				defaultShow: true
			}
		},
		computed: {
			times() { //使用計算屬性計算待辦todos的次數 
				let todoArr = this.todoLists
				let times = 0
				for (let i = 0; i < todoArr.length; i++) {
					if (todoArr[i].isChecked === false) {
						times++
					}
				}
				return times
			}
		},
		methods: {
			toEdit(obj) { //使新增的todo可編輯
				obj.isEditing = true
			},
			unEdit(obj) { //使新增的todo不可編輯
				obj.isEditing = false
			},
			addTodo(e) { //新增todo
				var val = e.value
				if (val === "") {
					return
				} //如果輸入內容為空則立即返回
				this.todoLists = this.todoLists.concat({ //使用concat這個api新增todo
					value: val, //輸入內容
					isEditing: false, //是否在編輯狀態
					isActive: false, //刪除X圖示是否啟用
					isChecked: false //是否已完成
				})
				this.$refs.currentInput.value = "" //按下enter新增todo之後把輸入框value清零
				window.localStorage.setItem("content", JSON.stringify(this.todoLists)) //使用localStorage以JSON格式儲存資料
			},
			deleteTodo(index) { //刪除todo
				this.todoLists.splice(index, 1)
				window.localStorage.setItem("content", JSON.stringify(this.todoLists)) //以json格式儲存資料
			},
			switchStatus(index) { //試下下方三個狀態切換,略麻煩
				this.dataStatusIndex = index
				if (this.dataStatus[index] === "Active") {
					this.defaultShow = false
					this.whichShow = false
				} else if (this.dataStatus[index] === "Completed") {
					this.defaultShow = false
					this.whichShow = true
				} else if (this.dataStatus[index] === "All") {
					this.defaultShow = true
				}
			},
			clearTodos() { //清空已完成的todoLists
				this.todoLists = this.todoLists.filter(todo => todo.isChecked === false)
				window.localStorage.setItem("content", JSON.stringify(this.todoLists)) //以json格式儲存資料
			},
			selectAllTodos() { //設定所有todo為已完成
				this.todoLists.map(todo => todo.isChecked = todo.isChecked ? false : true)
			}
		},
		directives: { //自定義focus指令
			"todo-focus": function (el, binding) {
				if (binding.value) {
					el.focus()
				}
			}
		},
		created() {
			let myStorage = window.localStorage.getItem('content')
			this.todoLists = JSON.parse(myStorage) || [] //因為todoLists初始值是null,使用或運算子,如果為null設為空陣列
		}
	})
</script>
複製程式碼

相關文章