Vue2.x 项目实战(一)

内容 参考链接
Vue2.x全家桶 Vue2.x 全家桶参考链接
Vue2.x项目(一) Vue2.x 实现一个任务清单
Vue2.x项目(二) Vue2.x 实现GitHub搜索案例
Vue3.x项目(三) Vue3.x 实现一个任务清单

文章目录

  • Vue2.x 项目实战(一)
    • Vue2.x 实现 todoList
      • 1、前言
      • 2、项目演示(一睹为快)
      • 3、涉及知识点
      • 4、项目详情(附源码及解析)
      • 5、写在最后的话

Vue2.x 实现 todoList

1、前言

如果你对 vue 的基础知识还很陌生,推荐先去学习一下 vue 基础

  • 如果你 刚学完 vue 基础知识,想检查一下自己的学习成果
  • 如果你 已学完 vue 基础知识,想快速回顾复习
  • 如果你 已精通 vue 基础知识,想做个小案例
  • 那不妨看完这篇文章,我保证你一定会有收获的!

2、项目演示(一睹为快)

todoList 项目演示

3、涉及知识点

  • Vue基础:插值语法,常用指令,键盘事件,列表渲染,计算属性,事件监听,生命周期
  • Vue进阶:props(父传子),自定义事件(任意组件间通信),自定义事件的解绑,$nextTick 异步
  • 本地存储:任务记录保留在当前浏览器中,长期有效(不手动销毁则一直保留)
  • 第三方库:nonoid(下载导入即可使用)

备注:

  1. 任意组件间的通信方式有很多种(全局事件总线,消息订阅预发布…),熟练掌握一种即可(推荐自定义事件,配置简单,容易理解)
  2. 本文是 vue 基础的练习项目,不涉及 vue 周边(Vuex,Vue-router)

4、项目详情(附源码及解析)

该项目有 五个组件 构成:

(1)App.vue 父组件,以上四个子组件 最终归并的地方,并实现很多功能相关方法

(2)MyHeader.vue 子组件:头部,用于用户文本框 输入添加任务事项

(3)MyList.vue 子组件:躯干,用于 呈现任务的列表

(4)MyItem.vue 子中子组件,Mylist.vue 的子组件,用于 呈现每个任务及编辑删除

(5)MyFooter 子组件,用于 显示所选个数和总个数及删除已完成任务

App.vue 父组件

  • 所有子组件的汇集点
  • 里面定义里很多方法,通过 props 父传子,供子组件们去使用
  • 当然也有自定义事件,供子给父传值,进行页面的渲染更新
<template><!-- 最外层容器 --><div class="todo-container"><div class="todo-wrap"><!-- 头部子组件,子传父,自定义 addTodo事件,添加一个 todo对象 --><MyHeader @addTodo="addTodo" /><!-- 任务列表子组件,父传子,动态绑定对应事件 --><MyList :updateTodo="updateTodo" :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /><!-- 底部子组件,子传父,全选和全清除 --><MyFooter:todos="todos"@checkAllTodo="checkAllTodo"@clearAllTodo="clearAllTodo"/></div></div>
</template><script>
// 引入所需组件
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";export default {name: "App",components: { MyHeader, MyList, MyFooter },data() {return {// 由于 todos 是 MyHeader 组件 和 MyFooter 组件都在用,所以放在APP中(状态提升)// 解析 JSON字符串 第一次使用时 null 身上没有 length 属性会报错,所以添加||,前面不能用时,置为空数组// localStorage.getItem("xxx") 用于从本地存储中读取 todostodos: JSON.parse(localStorage.getItem("todos")) || [],};},methods: {// 添加一个 todoaddTodo(todoObj) {this.todos.unshift(todoObj);},// 勾选 or 取消勾选一个todocheckTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done;});},// 更新一个 todoupdateTodo(id, title) {this.todos.forEach((todo) => {if (todo.id === id) todo.title = title;});},// 删除,todo.id !== id 就不会 push 该 todo,即删除deleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id);},// 全选 or 取消全选checkAllTodo(done) {this.todos.forEach((todo) => {todo.done = done;});},// 清除所有已经完成的todoclearAllTodo() {this.todos = this.todos.filter((todo) => {return !todo.done;});},},watch: {todos: {// 深度监视 检测到是否被勾选deep: true,handler(value) {// localStorage.setItem("xxx") 用来添加 todo// 格式化为 JSON 字符串localStorage.setItem("todos", JSON.stringify(value));},},},// 销毁前进行自定义事件的解绑beforeDestroy() {this.$off(['addTodo', 'checkAllTodo', 'clearAllTodo'])}
};
</script><style>
body {background: #fff;
}.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);border-radius: 4px;
}.btn-danger {color: #fff;background-color: #da4f49;border: 1px solid #bd362f;
}.btn-edit {margin-right: 5px;background-color: skyblue;border: 1px solid rgb(102, 158, 180);
}.btn-danger:hover {color: #fff;background-color: #bd362f;
}.btn:focus {outline: none;
}.todo-container {width: 600px;margin: 10px auto;
}.todo-container .todo-wrap {padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>

MyHeader.vue 组件

  • 终端键入 npm i nanoid,安装 nanoid
  • <style> 标签里的 scoped,表示里面定义的样式 仅在当前组件中生效
<template><div class="todo-header"><!-- 双向数据绑定 title,绑定键盘 enter 键,点击触发 add 事件,添加 title --><inputtype="text"placeholder="请输入你的任务名称,按回车键确认"v-model="title"@keyup.enter="add"/></div>
</template><script>
import { nanoid } from "nanoid";
export default {name: "MyHeader",data() {return {// 要输入的任务事项title: "",};},methods: {add() {// 校验数据if (!this.title.trim()) return alert("输入不能为空");// 将用户的输入包装成为一个 todo 对象,nanoid() 是随机生成的唯一值,默认为未完成事件const todoObj = { id: nanoid(), title: this.title, done: false };// 通知 App 组件去添加一个 todo 对象this.$emit("addTodo", todoObj);// 清空输入this.title = "";},},
};
</script><style scoped>
.todo-header input {width: 578px;height: 28px;font-size: 14px;border: 1px solid #ccc;border-radius: 4px;padding: 4px 7px;margin-bottom: 10px;
}.todo-header input:focus {outline: none;border-color: rgba(82, 168, 236, 0.8);box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);
}
</style>

MyList.vue 组件

  • 该组件即为 ul 标签包裹着 MyItem.vue 组件的果皮
  • 真正的果肉在 MyItem.vue 组件里面~~
<template><ul class="todo-main"><!-- :todo,动态绑定,供 MyItem.vue 使用 --><!-- 自定义 updateTodo 事件,子传父,供子组件编辑更新数据 --><MyItemv-for="todoObj in todos":key="todoObj.id":todo="todoObj":checkTodo="checkTodo":deleteTodo="deleteTodo"@updateTodo="updateTodo"/></ul>
</template><script>
import MyItem from "./MyItem.vue";
export default {name: "MyList",components: { MyItem },props: ["todos", "checkTodo", "deleteTodo", "updateTodo"],
};
</script><style scoped>
.todo-main {margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;
}.todo-empty {height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;
}
</style>

MyItem.vue 组件

  • 获取焦点的时候要用 $nextTick (等 DOM 节点更新后执行),或者用 setTimeout 异步包裹也能达到同样的效果
  • Vue2.x 不能监测对象属性的添加或删除。因为 Vue.js 在 初始化实例时 将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue2.x 转换它,才能让它是响应的。
  • 所以,当我们想要在 data 中或者 data 中的对象添加新的属性时,我们需要使用 Vue.set()vm.$set(),否则是无法触发视图更新的。
<template><li><label><!-- 复选框,:checked 单向绑定 todo 是否已完成,@change 检测复选框的变化 --><inputtype="checkbox":checked="todo.done"@change="handleCheck(todo.id)"/><!-- 非编辑状态下,在 sapn 标签中展示 todo --><span v-show="!todo.isEdit">{{ todo.title }}</span><!-- 绑定失去焦点事件,更新内容。ref 打标识,用于自动获取焦点 --><inputtype="text"style="height: 22px"v-show="todo.isEdit":value="todo.title"@blur="handleBlur(todo, $event)"ref="inputTitle"/></label><!-- 删除 todo --><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button><!-- 编辑状态下,展示输入框,隐藏编辑按钮。 --><button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button></li>
</template><script>
export default {name: "MyItem",// 声明接收 todo 对象,checkTodo 是否勾选,deleteTodo 删除该 todoprops: ["todo", "checkTodo", "deleteTodo"],methods: {// 勾选 or 取消勾选handleCheck(id) {// 通知 APP 组件 将对应的 todo 对象的 done 值取反this.checkTodo(id);},// 删除 todohandleDelete(id) {if (confirm("确定删除当前任务吗?")) {this.deleteTodo(id);}},// 编辑handleEdit(todo) {// 如果 todo 身上有 isEdit,则直接修改 isEdit,否则再给 todo 添加新的 isEdit// Reflect.has(todo, 'isEdit') 或 todo.hasOwnProperty.call(todo, "isEdit")if (Reflect.has(todo, 'isEdit')) {todo.isEdit = true;} else {this.$set(todo, "isEdit", true);}// DOM 节点更新后执行this.$nextTick(() => {this.$refs.inputTitle.focus()})},// 失去焦点,编辑框隐藏,并判断编辑后的内容是否为空,再呈现编辑后的内容handleBlur(todo, e) {todo.isEdit = false;if(!e.target.value.trim()) return alert('输入内容不能为空!')this.$emit('updateTodo', todo.id, e.target.value)},},
};
</script><style scoped>
span {color: orange;
}li {list-style: none;height: 36px;line-height: 36px;padding: 0 5px;border-bottom: 1px solid #ddd;
}li label {cursor: pointer;
}input {margin-right: 5px;
}li label li input {vertical-align: middle;margin-right: 6px;position: relative;top: -1px;
}li button {float: right;display: none;margin-top: 3px;
}li:before {content: initial;
}li:last-child {border-bottom: none;
}li:hover {background-color: #ddd;
}
li:hover button {display: block;
}
</style>

MyFooter.vue 组件

  • 底部的展示,当没有任务时隐藏该组件
  • reduce() 是一个高阶函数,接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值 参考链接
<template><!-- total 不为 0 则显示底部,否则隐藏 --><div class="todo-footer" v-show="total"><label><!-- 是否全选,双向绑定 isAll --><input type="checkbox" v-model="isAll" /></label><!-- 插值语法呈现数值 --><span class="done">已完成 {{ doneTotal }}</span>/<span class="total">全部 {{ total }}</span><button class="btn btn-danger" @click="clearAll()">清除已完成任务</button></div>
</template><script>
export default {name: "MyFooter",props: ["todos"],computed: {// 返回 todos 的总长度total() {return this.todos.length;},// 统计任务已经完成的个数doneTotal() {// reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值// pre 必需:初始值;todo 必需:当前元素;0 可选:传递给函数的初始值return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);},// 是否全选,当被选个数和总个数相同,且总个数大于 0 时,checked 选中isAll: {get() {return this.doneTotal === this.total && this.total > 0;},set(value) {this.$emit("checkAllTodo", value);},},},methods: {// 清除所有已完成任务clearAll() {this.$emit("clearAllTodo");},},
};
</script><style scoped>
.done {font-weight: bold;color: skyblue;
}.total {font-weight: bold;color: palevioletred;
}.todo-footer {height: 40px;line-height: 40px;padding-left: 6px;margin-top: 5px;
}.todo-footer label {display: inline-block;margin-right: 20px;cursor: pointer;
}.todo-footer label input {position: relative;top: -3px;vertical-align: middle;margin-right: -10px;
}.todo-footer button {float: right;margin-top: 5px;
}
</style>

5、写在最后的话

如果你是 看完全篇 阅读到了这里,我相信你一定是有收获的!

那么下面不妨打开自己的电脑,启动自己的编译器,来跟着做 / 自己做一遍吧!

好吧,我骗了你,真正学会它可能不止两个小时,但再多花点时间,你对 vue 的理解可能会有质的提升,加油~

如果这篇文章对你有些许帮助的话,不妨 三连 + 关注 支持一下~~

下一篇是 github 的搜索 demo,也是使用的 vue2.x 实现的,一起期待一下吧~



Vue项目实战——实现一个任务清单(学以致用,两小时带你巩固和强化Vue知识点)相关推荐

  1. Vue项目实战——实现一个任务清单【基于 Vue3.x 全家桶(简易版)】

    Vue3.x 项目实战(一) 内容 参考链接 Vue2.x全家桶 Vue2.x 全家桶参考链接 Vue2.x项目(一) Vue2.x 实现一个任务清单 Vue2.x项目(二) Vue2.x 实现Git ...

  2. vue 将字符串最后一个字符给替换_前端开发:Vue项目实战-Music

    大家好,我来了,本期为大家带来的前端开发知识是"前端开发:Vue项目实战-Music",有兴趣做前端的朋友,和我一起来看看吧! 主要内容 项目环境搭建 路由导航实现 ListVie ...

  3. vue 动态添加class_前端开发:Vue项目实战-Music

    大家好,我来了,本期为大家带来的前端开发知识是"前端开发:Vue项目实战-Music",有兴趣做前端的朋友,和我一起来看看吧! 主要内容 项目环境搭建 路由导航实现 ListVie ...

  4. 【VUE项目实战】68、使用pm2管理项目

    接上篇<67.上线-开启gzip和配置HTTPS服务> 上一篇我们学习了如何开启gzip配置来减少文件访问体积,并配置HTTPS服务.本篇我们讲解一下如何使用pm2管理项目. 本篇是该系列 ...

  5. 【VUE项目实战】32、权限管理-实现角色列表

    接上篇<31.权限管理-实现权限列表> 上个阶段我们完成了权限管理模块,本篇我们来介绍用户.角色和权限三者的关系,并完成角色管理列表模块. 一.权限管理业务分析 通过权限管理模块,控制不同 ...

  6. 【VUE项目实战】64、CND优化ElementUI以及首页内容定制

    接上篇<63.指定打包入口及加载外部CDN资源> 上一篇我们为开发模式与发布模式制定不同的打包入口,然后为项目加载外部CDN资源.本篇我们来学习通过CND优化ElementUI的打包,以及 ...

  7. Vue项目实战 —— 哔哩哔哩移动端开发—— 第一篇

    目录 前言完 效果图 : 登录含签权 注册带正则 个人中心 下拉加载更多主页 修改个人中心 视频播放加关注+收藏 评论盖楼A回复B B回复C C回复A类似 项目开始 封装登录.注册 封装登录 从零到一 ...

  8. 【VUE项目实战】59、订单的物流信息查询功能

    接上篇<58.订单修改收货地址的功能> 上一篇我们完成了订单列表的修改收货地址功能,本篇我们来实现订单的物流信息查询功能. 一.要实现的效果 我们要实现点击操作列的"物流进度&q ...

  9. 【VUE项目实战】56、商品添加功能(六)-提交添加的商品

    接上篇<55.商品添加功能(五)-商品内容模块> 上一篇我们完成了商品内容编辑模块的开发,也即是完成了商品所有的信息编辑,本篇我们就来开发提交商品所有信息到后台的功能. 一.要实现的效果 ...

最新文章

  1. bind函数怎么用JAVA_c++bind函数的用法
  2. 计算机的特别及应用,[计算机软件及应用]Excel使用技巧-非常全.doc
  3. struts -Tiles介绍
  4. 实战~~整个网络无法浏览,提示网络不存在或者尚未启动
  5. 算法前戏 递归 二分查找 列表查找
  6. MySQL流浪记(六)—— CONCAT到GROUP_CONCAT的沿途风景(图文详解)
  7. 自动推荐图表、智能分析,这个分析工具有点酷!
  8. 串口 能 按位传输吗_、 迪文串口屏TTL与主控板RS232电平信号转换方案
  9. Asp.net:DataList分页技术
  10. hdu 4864 task 贪心
  11. JAVA程序员的学习网站(2)
  12. 使用POI完成excel文件导出
  13. linux之文本编辑器
  14. VirtualBox 搭建android-x86
  15. Power BI桌面版与Online版功能区别
  16. 计算机如何打开office,win10电脑没有office软件如何打开ppt文档
  17. Pytorch警告UserWarning: Loky-backed parallel loops cannot be called in a multiprocessing
  18. 谁又在乎过你呢“main()之泪伤”
  19. STM32硬件实现 CRC-16/MODBUS
  20. 单片机消抖c语言程序,基于单片机定时器软件消抖C51程序研究

热门文章

  1. ajax前后端通信的头部消息之请求头与响应头
  2. 心疼南威尔士警方!成功演绎如何用人脸识别错抓罪犯
  3. 福莱特法公式matlab,浙江省公路出行交通分布模型研究
  4. [晕事]今天做了件晕事7
  5. 新能源电动车充电隐患不得不防,这些常识要知道
  6. 一级消防工程师【技术实务】(爆炸)
  7. 面试官问你有什么优点/缺点该如何回答
  8. pma连接,报错10061
  9. git error: You have not concluded your merge (MERGE_HEAD exists).
  10. 基于Python的个人足迹地图绘制设计