/**PageBeginHtml Block Begin **/ /***自定义返回顶部小火箭***/ /*生成博客目录的JS 开始*/ /*生成博客目录的JS 结束*/

Vue 中的 Todo-list 案例


Vue 中的  Todo-list 案例




1:示例

image


总结TodoList案例
组件化编码流程:

​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

​ 1).一个组件在用:放在组件自身即可。

​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

​ (3).实现交互:从绑定事件开始。

props适用于:

​ (1).父组件 ==> 子组件 通信

​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。



2: 代码结构

image





3:代码内容

index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <!-- 针对ie浏览器的一个特殊配置,含义是让ie浏览器以最高的渲染级别进行渲染界面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- 开启移动端理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!-- 配置页签的图标 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 配置网页的标题:找 package.json文件里的 "name": "vue_test" 值 -->
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <!-- 如果浏览器不支持js,则该标签的元素里的内容将会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>



vue.config.js

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave:false /*关闭语法检查*/
})





App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <ToListTop :addTodoInfos="addTodoInfos" /><!-- :addTodoInfos="addTodoInfos" 跨组件传递方法并符合数据对象 -->
        <ToList :todos="todos"
                :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
                :deleteTodosById="deleteTodosById"
        />  <!-- :todos="todos" 跨组件进行传值;:onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo":组件间数据方法的逐层传递 -->
        <ToListFooter  :todos="todos" :checkAllTodos="checkAllTodos" :ckearAllTodo="ckearAllTodo" />
      </div>
    </div>
  </div>
</template>
<script>

import ToListTop from './components/ToListTop.vue';
import ToList from './components/ToList.vue';
import ToListFooter from './components/ToListFooter.vue';


export default {
  name: 'App',
  components: {
    ToListTop: ToListTop,
    ToList: ToList,
    ToListFooter: ToListFooter
  },
  data() {
    return {
      todos: [
        { id: '001', title: '看书', done: true },
        { id: '002', title: '打羽毛球', done: false },
        { id: '003', title: '打乒乓球', done: false },
        { id: '004', title: '打篮球', done: false },
        { id: '005', title: '散步', done: false },
        { id: '006', title: '爬山', done: false },
        { id: '007', title: '吃饭', done: true }
      ]
    }
  },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
      // 添加1个todaoObj
      addTodoInfos(todoObj){
         console.log("App组件,接收到数据",todoObj);
         this.todos.unshift(todoObj);
      },
      // 勾选或者取消勾选1个todoObj
      onChangeTodosOfDoneInfo(id){
          this.todos.forEach((todo)=>{
             if(todo.id==id){
               todo.done =!todo.done; //赋值取反操作
             }
          });
      },
      //删除一个todo
      deleteTodosById(id){
        this.todos=this.todos.filter((todo) =>{
           return todo.id !==id;
        });
      },
      // 全选  或者  取消全选
      checkAllTodos(done){
        this.todos.forEach((todo)=>{
            todo.done=done;
        });
      },
      // 清除所有已经完成的todo事项
      ckearAllTodo(){
        this.todos=this.todos.filter((todo)=>{
          return !todo.done;
        });
      }
    }

}
</script>
<!--
  1指定Css 样式的编写方式
  less 最大特点内容可以嵌套着写

 -->
<style lang="less">
.divCss {
  background-color: chocolate;
  margin: auto;
  padding: 20px;

  .h1Css {
    font-size: 36px;
    color: white;
  }
}

/*base*/
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-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>



main.js

markdown//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue生产提示
Vue.config.productionTip=false;

// 创建Vm
const vm = new Vue(  {
        el:'#app',
        render: (h) => h(App)
   });


ToListTop.vue

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title"  @keyup.enter="addTodoInfo"/>
    </div>
</template>
<script>

import {nanoid} from 'nanoid'

export default {
    name: 'ToListTop',
    mixins: [],
    components: {},
    props: ['addTodoInfos'],/* addTodoInfos:接收到父组件的传递过来的方法 ,不同组件的相互调用的方法不能重名*/
    data() {
        return {
            title:""
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
        addTodoInfo(event){
            // 校验数据
            if(this.title.trim()=="" /* event.target.value */){
                return  alert("输入内容不能为空");
            }
            const todo={id:nanoid(),title:this.title/* event.target.value */,done:false};
            console.log(todo);
            this.addTodoInfos(todo);/* 将子组件的数据通过调用父组件的方式传递给父组件里的todos对象里 */
            /* event.target.value=""; */
            this.title=""; //清空数据
        }
    }
}
</script>
<style scoped lang="less">
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.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>


ToList.vue

<template>
    <ul class="todo-main">
        <ToItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
            :onChangeTodosOfDoneInfo="onChangeTodosOfDoneInfo"
            :deleteTodosById="deleteTodosById"
        />
    </ul>
</template>
<script>
import ToItem from './ToItem.vue';


export default {
    name: 'ToList',
    mixins: [],
    components: {
        ToItem: ToItem
    },
    props: ['todos', 'onChangeTodosOfDoneInfo','deleteTodosById'],
    data() {
        return {

        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {}
}
</script>
<style scoped lang="less">
/*main*/
.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>



ToItem.vue

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done"  @change="handleCheck(todo.id)" />
            <!-- 下面的方案可以 替代 @change="handleCheck(todo.id)" 作功能实现,不推荐。因为违反原则,修改里props里的对象数据,vue没有监测到 -->
            <!-- <input type="checkbox"  v-model="todo.done" /> --><!-- 一般不建议这么使用  v-model="todo.done"  调用 props传递过来的对象里的数据信息进行数据操作改变-->
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="delTodosInfo(todo.id)">删除</button><!-- style="display:none"  控制按钮隐藏 -->
    </li>
</template>
<script>
export default {
    name: 'ToItem',
    // 说明接收todo对象
    props:['todo','onChangeTodosOfDoneInfo','deleteTodosById'],
    mixins: [],
    components: {},
    data() {
        return {
        }
    },
    computed: {},
    watch: {},
    created() { },
    mounted() { },
    methods: {
      //勾选 或者 取消勾选
        handleCheck(id){
            //通知
            this.onChangeTodosOfDoneInfo(id);
        },
        // 删除
        delTodosInfo(id){
            if(confirm('确定删除吗?')){
              console.log(id);
              this.deleteTodosById(id);
            }
        }

    }
}
</script>
<style scoped lang="less">
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

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>


ToListFooter.vue


<template>
    <div class="todo-footer" v-show="total">
          <label>
           <!--  <input type="checkbox" :checked="isALL"  @change="checkAll"/> -->
            <input type="checkbox" v-model="isALL"/>
          </label>
          <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
          </span>
          <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
        </div>
</template>
<script>
export default {
    name: 'ToListFooter',

    mixins: [],
    components: {},
    props: ['todos','checkAllTodos','ckearAllTodo'],
    data() {
        return {
        }
    },
    computed: {
      // 统计总数
      total(){
        return this.todos.length;
      },
      // 统计勾选数量
      doneTotal(){
        // 计数统计实现方式一:
          let total=0;
          this.todos.forEach((todo)=>{
                if(todo.done){
                  total++;
                }
          } );
        // return total; 
        //计数统计实现方式二: 
         const td= this.todos.reduce((index,todo)=>{
            return index+(todo.done?1:0);
         },0);
         console.log("返回已完成结果值:",td);
        // 返回结果值
        //  return td;

         //计数统计实现方式三:
        return this.todos.reduce((index,todo)=>index+(todo.done?1:0),0);
      },
      // 操作全选,取消全选
      /* isAll(){ return this.doneTotal === this.total && this.total > 0; } */
      isALL:{
       get(){
         return this.doneTotal === this.total && this.total > 0;
       },
       set(checked){
          return this.checkAllTodos(checked);
       }
      }
    },
    watch: {},
    created() { },
    mounted() { },
    methods: {
      // 全选,或者 取消全选
      checkAll(event){
          this.checkAllTodos(event.target.checked);
      },
      // 清除已完成事项
      clearAll(){
        this.ckearAllTodo();
      }
     }

}
</script>
<style scoped lang="less">
/*footer*/
.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: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>












4:界面效果展示

image

image



5:安装nanoid 组件

image

posted @ 2022-12-11 09:54  一品堂.技术学习笔记  阅读(376)  评论(0编辑  收藏  举报