1. 初始化项目
1.在终端运行以下的命令,初始化 vite 项目:
npm init vite-app mytodolist2
2.切换路径
cd mytodolist2
3.使用 vscode 打开项目,并安装依赖项
npm install
4.启动项目
npm run dev
测试一下:结果如下
2.梳理项目结构
- 重置 index.css 中的全局样式如下:
/*所有元素*/
:root {
font-size: 12px;
}
body {
padding: 8px;
}
- 重置 App.vue 组件的代码结构如下:
<template>
<div>
<h1>App 根组件</h1>
</div>
</template>
<script>
export default {
name: 'MyApp',
data() {
return {
// 任务列表的数据
todolist: [
{ id: 1, task: '周一早晨9点开会', done: false },
{ id: 2, task: '周一晚上8点聚餐', done: false },
{ id: 3, task: '准备周三上午的演讲稿', done: true },
],
}
},
}
</script>
<style lang="less" scoped></style>
当前效果:
3. 封装 todo-list 组件
3.1 创建并注册 TodoList 组件
- 在
src/components/todo-list/
目录下新建TodoList.vue
组件:
<template>
<div>TodoList 组件</div>
</template>
<script>
export default {
name: 'TodoList',
}
</script>
<style lang="less" scoped></style>
- 在
App.vue
组件中导入并注册TodoList.vue
组件:
// 导入 TodoList 组件
import TodoList from './components/todo-list/TodoList.vue'
export default {
name: 'MyApp',
// 注册私有组件
components: {
TodoList,
}
}
- 在
App.vue
的 template 模板结构中使用注册的TodoList
组件:
<template>
<div>
<h1>App根组件</h1>
<!-- 使用 todo-list 组件 -->
<todo-list></todo-list>
</div>
</template>
3.2 基于 bootstrap 渲染列表组件
- 将
资料
目录下的css
文件夹拷贝到src/assets/
静态资源目录中。 - 在
main.js
入口文件中,导入src/assets/css/bootstrap.css
样式表:
import { createApp } from 'vue'
import App from './App.vue'
// 导入 bootstrap.css 样式表
import './assets/css/bootstrap.css'
import './index.css'
createApp(App).mount('#app')
- 根据 bootstrap 提供的列表组( https://v4.bootcss.com/docs/components/list-group/#with
-badges )和复选框(https://v4.bootcss.com/docs/components/forms/#checkboxes-and-radio
s-1 )渲染列表组件的基本效果:
<template>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center">
<!-- 复选框 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck1" />
<label class="custom-control-label" for="customCheck1">Check this custom checkbox</label>
</div>
<!-- badge 效果 -->
<span class="badge badge-success badge-pill">完成</span>
<span class="badge badge-warning badge-pill">未完成</span>
</li>
</ul>
</template>
ctrl+shift+f 可自动排版
当前效果如下:
3.3 为 TodoList 声明 props 属性
- 为了接受外界传递过来的列表数据,需要在
TodoList
组件中声明如下的 props 属性:
<script>
export default {
name: 'TodoList',
props: {
// 列表数据
list: {
type: Array,
required: true,
default: [],
},
}
}
</script>
- 在
App
组件中通过list
属性,将数据传递到TodoList
组件之中:
<todo-list :list="todolist"></todo-list>
当前效果:
3.4 渲染列表的 DOM 结构
- 通过 v-for 指令,循环渲染列表的 DOM 结构:
<template>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center"
v-for="item in list" :key="item.id">
<!-- 复选框 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="item.id" />
<label class="custom-control-label" for="item.id">{{item.task}}</label>
</div>
<!-- badge 效果 -->
<span class="badge badge-success badge-pill" v-if="item.done ">完成</span>
<span class="badge badge-warning badge-pill" v-else>未完成</span>
</li>
</ul>
</template>
当前效果如下:
2. 通过 v-if 和 v-else 指令,按需渲染 badge 效果:
<!-- badge 效果 -->
<span class="badge badge-success badge-pill" v-if="item.done ">完成</span>
<span class="badge badge-warning badge-pill" v-else>未完成</span>
- 通过 v-model 指令,双向绑定任务的完成状态:
<!-- 复选框 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="item.id"
v-model="item.done"/>
<!-- 注意:App 父组件通过 props 传递过来的 list 是“引用类型”的数据, -->
<!-- 这里 v-model 双向绑定的结果是:用户的操作修改的是 App 组件中数据的状态 -->
- 通过 v-bind 属性绑定,动态切换元素的 class 类名:
<label class="custom-control-label"
:class="item.done ? 'delete' :''" :for="item.id">{{item.task}}</label>
在 TodoList
组件中声明如下的样式,美化当前组件的 UI 结构:
<style lang="less" scoped>
// 为列表设置固定宽度
.list-group {
width: 400px;
}
// 删除效果
.delete {
text-decoration: line-through;
}
</style>
当前效果:
4. 封装 todo-input 组件
4.1 创建并注册 TodoInput 组件
- 在
src/components/todo-input/
目录下新建TodoInput.vue
组件:
<template>
<div>TodoInput 组件</div>
</template>
<script>
export default {
name: 'TodoInput',
}
</script>
<style lang="css" scoped></style>
- 在
App.vue
组件中导入并注册TodoInput.vue
组件:
// 导入 TodoList 组件
import TodoList from './components/todo-list/TodoList.vue'
// 导入 TodoInput 组件
import TodoInput from './components/todo-input/TodoInput.vue'
components:{ //注册组件
TodoList,TodoInput,
}
- 在
App.vue
的 template 模板结构中使用注册的TodoInput
组件:
<!-- 使用 TodoInput 组件 -->
<todo-input></todo-input>
当前效果:
4.2 基于 bootstrap 渲染组件结构
- 根据 bootstrap 提供的 inline-forms(https://v4.bootcss.com/docs/components/forms/#in
line-forms )渲染 TodoInput 组件的基本结构。 - 在
TodoInput
组件中渲染如下的 DOM 结构:
<template>
<!-- form 表单 -->
<form class="form-inline">
<div class="input-group mb-2 mr-sm-2">
<!-- 输入框的前缀 -->
<div class="input-group-prepend">
<div class="input-group-text">任务</div>
</div>
<!-- 文本输入框 -->
<input type="text" class="form-control" placeholder="请填写任务信息" style="width: 356px;" />
</div>
<!-- 添加按钮 -->
<button type="submit" class="btn btn-primary mb-2">添加新任务</button>
</form>
</template>
<script>
export default {
name: 'TodoInput',
}
</script>
<style lang="css" scoped></style>
4.3 通过自定义事件向外传递数据
- 在
TodoInput
组件的 data 中声明如下的数据:
data(){
return{
//新任务
taskname:'',
}
}
- 为
input
输入框进行v-model
的双向数据绑定:
<input type="text" class="form-control" placeholder="请填写任务信息"
style="width: 356px;" v-model.trim="taskname" />
- 监听
form
表单的 submit 事件,阻止默认提交行为并指定事件处理函数:
<form class="form-inline" @submit.prevent="onFormSubmit"></form>
- 在
methods
中声明onFormSubmit
事件处理函数如下:
methods: {
// 表单提交的事件处理函数
onFormSubmit() {
// 1. 判断任务名称是否为空
if (!this.taskname) return alert('任务名称不能为空!')
// 2. 触发自定义的 add 事件,并向外界传递数据
// 3. 清空文本框
},
}
- 声明自定义事件如下:
//声明自定义事件
emits:['add'],
- 进一步完善 onFormSubmit 事件处理函数如下:
methods: {
// 表单提交的事件处理函数
onFormSubmit() {
// 1. 判断任务名称是否为空
if (!this.taskname) return alert('任务名称不能为空!')
// 2. 触发自定义的 add 事件,并向外界传递数据
this.$emit('add', this.taskname)
// 3. 清空文本框
this.taskname = ''
},
}
4.4 实现添加任务的功能
- 在
App.vue
组件中监听TodoInput
组件自定义的add
事件:
<!-- 使用 TodoInput 组件 -->
<!-- 监听 TodoInput 的 add 自定义事件 -->
<todo-input @add="onAddNewTask"></todo-input>
- 在 App.vue 组件的 data 中声明 nextId 来模拟 id 自增 +1 的操作。
// 下一个可用的 Id 值
nextId: 4,
- 在
App.vue
组件的methods
中声明onAddNewTask
事件处理函数如下:
methods: {
// TodoInput 组件 add 事件的处理函数
onAddNewTask(taskname) {
// 1. 向任务列表中新增任务信息
this.todolist.push({
id: this.nextId,
task: taskname,
done: false, // 完成状态默认为 false
})
// 2. 让 nextId 自增+1
this.nextId++
},
}
当前效果:
5. 封装 todo-button 组件
5.1 创建并注册 TodoButton 组件
- 在
src/components/todo-button/
目录下新建TodoButton.vue
组件:
<template>
<div>TodoButton 组件</div>
</template>
<script>
export default {
name: 'TodoButton',
}
</script>
<style lang="css" scoped></style>
- 在
App.vue
组件中导入并注册TodoButton.vue
组件:
// 导入 TodoButton 组件
import TodoButton from './components/todo-button/TodoButton.vue'
components:{ //注册组件
TodoList,
TodoInput,
TodoButton,
}
5.2 基于 bootstrap 渲染组件结构
- 根据 bootstrap 提供的 Button group(https://v4.bootcss.com/docs/components/button-g
roup/ )渲染 Todobutton 组件的基本结构。 - 在
TodoButton
组件中渲染如下的 DOM 结构:
<template>
<div class="button-container mt-3">
<div class="btn-group">
<button type="button" class="btn btn-primary">全部</button>
<button type="button" class="btn btn-secondary">已完成</button>
<button type="button" class="btn btn-secondary">未完成</button>
</div>
</div>
</template>
- 并通过 button-container 类名美化组件的样式:
<style lang="css" scoped>
.button-container {
/*添加固定宽度*/
width: 400px;
/*文本居中效果*/
text-align: center;
}
</style>
5.3 通过 props 指定默认激活的按钮
- 在
TodoButton
组件中声明如下的 props,用来指定默认激活的按钮的索引:
<script>
export default {
name: 'TodoButton',
props: {
// 激活项的索引值
active: {
type: Number,
required: true,
// 默认激活索引值为 0 的按钮(全部:0,已完成:1,未完成:2)
default: 0,
},
},
}
</script>
当前效果:
2. 通过 动态绑定 class 类名 的方式控制按钮的激活状态:
<template>
<div class="button-container mt-3">
<div class="btn-group">
<button type="button" class="btn"
:class="active === 0 ?'btn-primary' : 'btn-secondary'">全部</button>
<button type="button" class="btn"
:class="active === 1 ?'btn-primary' : 'btn-secondary'">已完成</button>
<button type="button" class="btn"
:class="active === 2 ?'btn-primary' : 'btn-secondary'">未完成</button>
</div>
</div>
</template>
- 在
App
组件中声明默认激活项的索引,并通过属性绑定的方式传递给TodoButton
组件:
data() {
return {
// 激活的按钮的索引
activeBtnIndex: 0
}
}
<!-- 使用 TodoButton 组件 -->
<todo-button :active="activeBtnIndex"></todo-button>
目前效果如下:
5.4 通过 v-model 更新激活项的索引
- 为
TodoButton
组件中的三个按钮分别绑定click
事件处理函数如下:
<button type="button" class="btn" @click="onBtnClick(0)"
:class="active === 0 ?'btn-primary' : 'btn-secondary'">全部</button>
<button type="button" class="btn" @click="onBtnClick(1)"
:class="active === 1 ?'btn-primary' : 'btn-secondary'">已完成</button>
<button type="button" class="btn" @click="onBtnClick(2)"
:class="active === 2 ?'btn-primary' : 'btn-secondary'">未完成</button>
- 在
TodoButton
组件的methods
节点中声明onBtnClick
事件处理函数如下:
//1.声明自定义事件changeactive
emits:['changeactive'],
methods:{
onBtnClick(n){
//触发自定义事件changeactive
this.$emit('changeactive',n)
}
}
3.在App.vue中修改如下
<todo-button :active="activeBtnIndex"
@changeactive="changetasklist" ></todo-button>
changetasklist(n){
alert(n)
}
当前效果:
点击全部,弹出对话框显示0;点击已完成,弹出对话框显示1;点击未完成,弹出对话框显示2
在App.vue中修改如下
changetasklist(n){
//n表示单击的是哪一个按钮
this.activeBtnIndex = n
}
效果:点击哪一个,哪一个变为蓝色
5.5 通过计算属性动态切换列表的数据
- 在 App 根组件中声明如下的计算属性:
computed:{ // 计算属性
tasklist(){
// activeBtnIndex发生变化,就会自动执行tasklist计算属性
switch(this.activeBtnIndex){
case 0:
return this.todolist
case 1:
return this.todolist.filter( (x) => {return x.done} )
case 2:
return this.todolist.filter( (x) => {return !x.done} )
}
}
}
- 在
App
根组件的 DOM 结构中,将TodoList
组件的:list="todolist"
修改为:
<todo-list :list="tasklist"></todo-list> <!-- 3.使用todo-list组件 -->
案例完整代码
TodoButton.vue
<template>
<div class="button-container mt-3">
<div class="btn-group">
<button type="button" class="btn" @click="onBtnClick(0)"
:class="active === 0 ?'btn-primary' : 'btn-secondary'">全部</button>
<button type="button" class="btn" @click="onBtnClick(1)"
:class="active === 1 ?'btn-primary' : 'btn-secondary'">已完成</button>
<button type="button" class="btn" @click="onBtnClick(2)"
:class="active === 2 ?'btn-primary' : 'btn-secondary'">未完成</button>
</div>
</div>
</template>
<script>
export default {
name: 'TodoButton',
props:{
// 当前激活的按钮
active:{
type:Number,
required:true,
default:1,
}
},
//1.声明自定义事件changeactive
emits:['changeactive'],
methods:{
onBtnClick(n){
//触发自定义事件changeactive
this.$emit('changeactive',n)
}
}
}
</script>
<style lang="css" scoped="scoped">
.button-container{
width:400px;
text-align: center;
}
</style>
TodoInput.vue
<template>
<!-- form 表单 -->
<form class="form-inline" @submit.prevent="onFormSubmit">
<div class="input-group mb-2 mr-sm-2">
<!-- 输入框的前缀 -->
<div class="input-group-prepend">
<div class="input-group-text">任务</div>
</div>
<!-- 文本输入框 -->
<input type="text" class="form-control" placeholder="请填写任务信息"
style="width: 356px;" v-model.trim="taskname" />
</div>
<!-- 添加按钮 -->
<button type="submit" class="btn btn-primary mb-2">添加新任务</button>
</form>
</template>
<script>
export default {
name: 'TodoInput',
//声明自定义事件
emits:['add'],
data(){
return{
//新任务
taskname:'',
}
},
methods: {
// 表单提交的事件处理函数
onFormSubmit() {
// 1. 判断任务名称是否为空
if (!this.taskname) return alert('任务名称不能为空!')
// 2. 触发自定义的 add 事件,并向外界传递数据
this.$emit('add', this.taskname)
// 3. 清空文本框
this.taskname = ''
},
}
}
</script>
<style lang="css" scoped></style>
TodoList.vue
<template>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center"
v-for="item in list" :key="item.id">
<!-- 复选框 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input"
:id="item.id" v-model="item.done" />
<label class="custom-control-label":class="item.done ? 'delete' :
''" :for="item.id">{{item.task}}</label>
</div>
<!-- badge 效果 -->
<span class="badge badge-success badge-pill" v-if="item.done">完成</span>
<span class="badge badge-warning badge-pill" v-else>未完成</span>
</li>
</ul>
</template>
<script>
export default {
name: 'TodoList',
props:{
// 任务列表数据
list:{
type:Array, // 类型是数组
required:true,
default:[],
}
},
}
</script>
<style lang="css" scoped="scoped">
.list-group {
width: 400px; }
.delete {
text-decoration: line-through; }
</style>
App.vue
<template>
<div>
<!-- 3.监听add事件,执行addNewTask函数 -->
<todo-input @add='addNewTask'></todo-input>
<todo-list :list="tasklist"></todo-list> <!-- 3.使用todo-list组件 -->
<!-- 5.监听changeactive事件,触发函数changetasklist -->
<todo-button :active="activeBtnIndex"
@changeactive="changetasklist"
></todo-button>
</div>
</template>
<script>
// 1.导入组件
import TodoList from './components/todo-list/TodoList.vue'
import TodoInput from './components/todo-input/TodoInput.vue'
import TodoButton from './components/todo-button/TodoButton.vue'
export default {
name: 'App',
methods:{
// 4.事件函数,参数taskname是this.taskname
addNewTask(taskname){
this.todolist.push({
id:this.nextId,
task:taskname,
done:false, // 默认值是false
});
this.nextId ++
},
// 6.改变任务列表的函数
changetasklist(n){
// n表示单击的是哪一个按钮
this.activeBtnIndex = n
}
},
data(){
return{
todolist:[ // 任务列表
{id:1,task:'9点晨会',done:false},
{id:2,task:'10点聚餐',done:true},
{id:3,task:'11点准备演讲稿',done:false},
],
// 新任务id
nextId:4,
// 激活的按钮的索引
activeBtnIndex:0,
}
},
components: { // 2. 注册组件
TodoList,TodoInput,TodoButton,
},
computed:{ // 计算属性
tasklist(){
// activeBtnIndex发生变化,就会自动执行tasklist计算属性
switch(this.activeBtnIndex){
case 0:
return this.todolist
case 1:
return this.todolist.filter( (x) => {return x.done} )
case 2:
return this.todolist.filter( (x) => {return !x.done} )
}
}
}
}
</script>
<style lang="css" scoped="scoped"></style>
效果如下: