首页IT科技vue项目工作流程(Vue项目实战——实现一个任务清单(学以致用,两小时带你巩固和强化Vue知识点))

vue项目工作流程(Vue项目实战——实现一个任务清单(学以致用,两小时带你巩固和强化Vue知识点))

时间2025-05-04 23:13:17分类IT科技浏览4087
导读:Vue2.x 项目实战(一) 内容 参考链接 Vue2.x全家桶 Vue2.x 全家桶参考链接 Vue2.x项目(一) Vue2.x 实现一个任务清单 Vue2.x项目(二...

Vue2.x 项目实战(一)

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

Vue2.x 实现 todoList

1            、前言

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

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

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

todoList 项目演示

3      、涉及知识点

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

备注:

任意组件间的通信方式有很多种(全局事件总线   ,消息订阅预发布…)            ,熟练掌握一种即可(推荐自定义事件                  ,配置简单      ,容易理解) 本文是 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") 用于从本地存储中读取 todos todos: JSON.parse(localStorage.getItem("todos")) || [], }; }, methods: { // 添加一个 todo addTodo(todoObj) { this.todos.unshift(todoObj); }, // 勾选 or 取消勾选一个todo checkTodo(id) { this.todos.forEach((todo) => { if (todo.id === id) todo.done = !todo.done; }); }, // 更新一个 todo updateTodo(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; }); }, // 清除所有已经完成的todo clearAllTodo() { 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 --> <input type="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 事件                  ,子传父            ,供子组件编辑更新数据 --> <MyItem v-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 检测复选框的变化 --> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <!-- 非编辑状态下                  ,在 sapn 标签中展示 todo --> <span v-show="!todo.isEdit">{{ todo.title }}</span> <!-- 绑定失去焦点事件      ,更新内容         。ref 打标识         ,用于自动获取焦点 --> <input type="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 删除该 todo props: ["todo", "checkTodo", "deleteTodo"], methods: { // 勾选 or 取消勾选 handleCheck(id) { // 通知 APP 组件 将对应的 todo 对象的 done 值取反 this.checkTodo(id); }, // 删除 todo handleDelete(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 实现的      ,一起期待一下吧~

声明:本站所有文章         ,如无特殊说明或标注                  ,均为本站原创发布         。任何个人或组织         ,在未征得本站同意时      ,禁止复制         、盗用      、采集                  、发布本站内容到任何网站            、书籍等各类媒体平台      。如若本站内容侵犯了原著者的合法权益                  ,可联系我们进行处理                  。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
python反斜杠用法(python中Faker库如何生成随机测试数据?) 简单介绍下vue的优缺点及应用(vue是什么?vue的优点有哪些?)