阅读 127

学习Vue 做一个简单的Todo List

一、创建Vue项目

创建项目的方式有很多,这就简单的说两种吧: 1、命令行创建 win+r 输入cmd 回车 cd进入你要创建的目录 输入下面的命令,遇到需要确认的地方按回车就行了,一般没有什么问题

vue init webpack todolist // todolist是项目名称
复制代码

创建成功以后 进入项目里面安装依赖运行

cd todolist
npm install // yarn 或  cnpm install
npm run dev
复制代码

2、如果使用的HBuilder开发工具,可以直接在工具中创建项目 点击左上角的文件->新建->项目 输入名称,选好文件夹,点击创建就可以了 在这里插入图片描述 项目创建完成后 1、在src目录下新建pages文件夹,里面创建todolist.vue文件 2、配置路由: 1)安装 npm i router 2)在src目录下新建router文件夹,再创建一个index.js文件用来配置路由

import Vue from "vue"
import Router from "vue-router"
Vue.use(Router)

import Index from '@/pages/todoist'

const router = new Router({
	mode: 'history',
	routes: [
		{
			path: '/',
			name: 'index',
			component: Index
		},
	]
})

export default router

复制代码

3)main.js文件中引入路由

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import VueRouter from 'vue-router'

Vue.config.productionTip = false
Vue.use(VueRouter)

new Vue({
	router,
	render: h => h(App)
}).$mount('#app')

复制代码

最终 项目文件如下: 在这里插入图片描述

二、组件

todolist可以分为三个板块 1、头部(添加输入栏目) 2、列表(添加栏目列表) 3、底部(按钮等) 我们可以将这三个板块拆分成三个组件:

1、在todolist文件夹下新建components文件夹 2、在文件夹下新建三个组件: todoHeader, todoItem, todoFooter 3、引入组件

import todoHeader from './components/todoHeader.vue'
import todoItem from './components/todoItem.vue'
import todoFooter from './components/todoFooter.vue'
export default {
	components: {
		todoHeader,
		todoItem,
		todoFooter
	},
	data() {
		return {}
	},
	methods: {}
}
复制代码

三、组件代码

index.vue

<template>
	<div class="page flex">
		<div class="card">
			<h1>Todo List</h1>
			<todoHeader @confirm="confirm" />
			<todoItem :list="list" @change="changeList" />
			<todoFooter :list="list" @change="allChange" @delete="show_confirm" />
		</div>
	</div>
</template>

<script>
	import todoHeader from './components/todoHeader.vue'
	import todoItem from './components/todoItem.vue'
	import todoFooter from './components/todoFooter.vue'
	export default {
		components: {
			todoHeader,
			todoItem,
			todoFooter
		},
		data() {
			return {
				list: [
					// {
					// 	value: '吃饭',
					// 	checked: false
					// },
					// {
					// 	value: '睡觉',
					// 	checked: false
					// }
				]
			}
		},
		methods: {
			changeList({checked, index}) {
				this.list[index].checked = checked
			},
			confirm(value) {
				this.list.push({
					value,
					checked: false
				})
			},
			allChange(checked) {
				this.list.map(item => {
					item.checked = checked
				})
			},
			show_confirm() {
				if (this.list.length > 0) {
					let flag = false
					this.list.map(item => {
						if (item.checked) {
							flag = true
						}
					})
					if (flag) {
						let modal = confirm("确定要删除已经完成的任务吗?");
						if (modal) {
							this.del()
						}
					} else {
						alert('只能删除已经完成的任务!')
					}
				} else {
					alert('没有可删除的任务!')
				}
				
			},
			del() {
				let arr = []
				this.list.map((item, index) => {
					if (!item.checked) {
						arr.push(item)
					}
				})
				
				this.list = arr
			}
		}
	}
</script>

<style>
	.page{
		width: 100%;
		margin-top: 50px;
		justify-content: center;
	}
	
	.flex{
		display: flex;
		align-items: center;
	}
	
	.flex-1{
		flex: 1;
	}
	
	.card{
		width: 500px;
		padding: 15px 16px;
		border-radius: 4px;
		border: 1px solid #DCDFE6;
	}
	
	.card:hover{
		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
	}
	
	h1{
		text-align: center;
		margin-bottom: 20px;
	}
</style>

复制代码

todoHeader.vue

<template>
	<div class="header flex">
		<div class="inputs flex-1" :class="isfocus ? 'focus' :''">
			<input placeholder="请输入你的任务名称,按回车键确定" class="input" type="text" v-model="value" @focus="focus" @blur="blur" @keyup.enter="confirm" />
		</div>
		<button v-show="isfocus" class="btn" type="button">Add</button>
	</div>
</template>

<script>
	export default {
		data() {
			return {
				isfocus: false,
				value: ''
			}
		},
		methods: {
			focus() {
				this.isfocus = true
			},
			blur() {
				this.isfocus = false
			},
			confirm() {
				this.$emit('confirm', this.value)
				this.value = ''
			}
		}
	}
</script>

<style>
	.header{
		width: 100%;
		margin-bottom: 20px;
		justify-content: space-between;
	}
	
	.inputs{
		border-radius: 4px;
		border: 1px solid #DCDFE6;
		padding: 6px 10px;
	}
	
	.inputs.focus{
		border: 1px solid #409EFF;
		box-shadow: 0 1px 8px 0 rgba(64, 158, 255, 0.4)
	}
	
	.input{
		width: 100%;
		height: 20px;
	}
	
	.btn{
		padding: 6px 10px;
		background-color: red;
		color: #FFFFFF;
		border-radius: 4px;
		cursor: pointer;
		margin-left: 20px;
	}
	
	.btn:hover{
		background-color: #F56C6C;
	}
</style>

复制代码

todoItem.vue

<template>
	<div class="list">
		<ul v-if="list.length">
			<li class="flex" v-for="(item, index) in list" :key="index">
				<div class="flex-1" @click="change(!item.checked, index)">
					<input class="checkbox" type="checkbox" :value="item.value" v-model="item.checked" @change.stop="change(item.checked, index)" />
					<label class="label">{{ item.value }}</label>
				</div>
				<div v-if="item.checked" style="color: #C0C4CC;">已完成</div>
			</li>
		</ul>
		<div class="empty flex" v-else>
			<span>暂无任务</span>
		</div>
	</div>
</template>

<script>
	export default {
		props: {
			list: {
				type: Array,
				default: []
			}
		},
		data() {
			return {}
		},
		methods: {
			change(checked, index) {
				this.$emit('change', {checked, index})
			}
		}
	}
</script>

<style>
	.list{
		width: 100%;
		margin-bottom: 20px;
	}
	
	ul{
		width: 100%;
		border-radius: 4px;
		border: 1px solid #DCDFE6;
	}
	
	li{
		padding: 0 10px;
		border-bottom: 1px solid #DCDFE6;
		justify-content: space-between;
		cursor: pointer;
	}
	
	li > div{
		padding: 8px 0;
	}
	
	li > div >  .label{
		margin-left: 8px;
		font-size: 14px;
		line-height: 14px;
	}
	
	li:last-child{
		border-bottom: none;
	}
	
	li:hover{
		color: #409eff;
		background-color: #ecf5ff;
	}
	
	.empty{
		justify-content: center;
		width: 100%;
		height: 50px;
	}
	
	.empty span{
		color: #C0C4CC;
		font-size: 20px;
	}
</style>

复制代码

todoFooter.vue

<template>
	<div class="footer flex">
		<div class="flex">
			<input class="checkbox" type="checkbox" value="all" v-model="checked" @change="change(checked)" />
			<label class="label">
				已完成 ({{ count }})
				/
				全部 ({{ total }})
			</label>
		</div>
		<div>
			<button class="btn" type="button" @click="del">Delete</button>
		</div>
	</div>
</template>

<script>
	export default {
		props: {
			list: {
				type: Array,
				default: []
			}
		},
		data() {
			return {
				checked: false
			}
		},
		computed: {
			total() {
				return this.list.length
			},
			count() {
				let count = 0
				this.list.map(item => {
					if (item.checked) {
						count += 1
					}
				})
				return count
			}
		},
		methods: {
			change(checked) {
				this.$emit('change', checked)
			},
			del() {
				this.$emit('delete')
			}
		}
	}
</script>

<style>
	.footer{
		width: 100%;
		justify-content: space-between;
	}
	
	.btn{
		padding: 6px 10px;
		background-color: red;
		color: #FFFFFF;
		border-radius: 4px;
		cursor: pointer;
	}
	
	.btn:hover{
		background-color: #F56C6C;
	}
	
	.label{
		margin-left: 15px;
		line-height: 14px;
		font-size: 14px;
	}
</style>

复制代码

组件通信

1、父子组件之间往往是需要进行数据交互的,他们之间的数据传递的方法也有很多,这里使用的是props的方法

props: {
	list: {  // 传递的数据名称
		type: Array,  // 数据类型
		default: []  // 默认值
	}
}
复制代码

2、在子组件中想要调用父组件的方法这里是采用的$emit

// 子组件中
change(checked, index) {
	this.$emit('change', {checked, index})  // 第一个参数是传递的方法名称, 第二个参数是需要项外传递的参数
}  

// 父组件中
// 通过子组件中定义的change来接收执行方法
<todoItem :list="list" @change="changeList" />
复制代码

图片演示

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

文章分类
前端
文章标签