vue3+ts实现todolist功能

先看一下实现效果:

 可以看到内部实现的内容有enter输入,单项删除,全选,以及删除选中项等功能

具体在实现前需要常见有ts的vue3项目

项目创建

具体项目创建 就是 vue create 项目名称

在创建后,选择的时候有vue2和vue3的选择,第三项是自定义,在自定义时需要选中ts(选择的键分别是向下键和空格键)

在创建项目之后,先运行,查看是否可运行

TodoList实现

目录结构:

运行文件:App.vue

组件:components下的文件

配置文件:utils   types

文件内容按上述介绍展示:

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
     <Header :addTodo="addTodo"/>
     <List :todos='todos' :deleteTodo="deleteTodo" :updateTodo="updateTodo"/>
     <Footer :todos="todos" :checkAll="checkAll" :clearAllCompletedTodos="clearAllCompletedTodos"/>
    </div>
  </div>
</template>

<script lang='ts'>
import { defineComponent,reactive,toRefs,watch,onMounted } from 'vue'
// 引入直接的子集组件
import Header from './components/Header.vue'
import List from './components/List.vue'
import Footer from './components/Footer.vue'
import {Todo } from './types/todo'
import {saveTodos, readTodos} from './utils/localStorageUtils'

export default defineComponent({
  name:'App',
  components: {
    Header,
    List,
    Footer
  },
  // 数据存储为数组格式,数组内的为对象,对象中有三个属性(id, title, isSCompleted)
  // 把数据定义到App.vue父级组件
  setup(){
    // 定义一个数组数据
    // const state = reactive<{todos: Todo[]}>({
    //   todos: [
    //     {id: 1,title:'奔驰',isCompleted: false},
    //     {id: 2,title:'宝马',isCompleted: true},
    //     {id: 3,title:'奥迪',isCompleted: false},
    //   ]
    // })
    const state = reactive<{todos: Todo[]}>({
      todos: []
    })
    // 界面加载完毕后再读取数据
    onMounted(() => {
      setTimeout(() => {
        state.todos = readTodos()
      },1000)
    })

    // 添加数据的方法
    // eslint-disable-next-line
    const addTodo = (todo:Todo) => {
      state.todos.unshift(todo)
    }

    // 删除数据的方法
    const deleteTodo = (index:number) =>{
      state.todos.splice(index, 1)
    }

    // 修改todod的 isCompleted属性的状态
    const updateTodo = (todo: Todo,isCompleted: boolean) => {
      todo.isCompleted = isCompleted
      console.log(todo);
    }
    // 全选或者全不选的方法
    const checkAll = (isCompleted:boolean) => {
      // 遍历数组
      state.todos.forEach((todo) => {
        todo.isCompleted = isCompleted
      });
    }
    // 清理所有选中的数据
    const clearAllCompletedTodos = () => {
      state.todos = state.todos.filter(todo=>!todo.isCompleted)
    }

    // 监视操作:如果todos数组的数据变化了,直接存储到浏览器的缓存中
    // watch(() => state.todos, (value)=> {
    //   // 保存到浏览器缓存中
    //   localStorage.setItem('todos_key',value)
    // },{deep:true})

    // watch(() => state.todos, (value)=> {
    //   // 保存到浏览器缓存中
    //   saveTodos(value)
    // },{deep:true})

    watch(() => state.todos, saveTodos, {deep:true})
    return {
      ...toRefs(state),
      addTodo,
      deleteTodo,
      updateTodo,
      checkAll,
      clearAllCompletedTodos
    }
  }
})
</script>

<style scoped>
.todo-container{
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap{
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

components 下的 Header.vue

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

<script lang='ts'>
import { defineComponent,ref } from 'vue'

export default defineComponent({
  name:'Header',
  props: {
    addTodo: {
      type: Function,
      required: true    // 必须
    }
  },
  setup(props){
    // 定义一个ref类型的数据
    const title = ref('')
    // 回车的事件回调函数,用来添加数据
    const add = () => {
      // 获取文本框中输入的数据,判断不能为空
      const text = title.value
      if(!text.trim()) return
      //此时有数据,创建一个todo对象
      const todo = {
        id: Date.now(),
        title: text,
        isCompleted: false
      }
      // 调用方法addTodo方法
      props.addTodo(todo)
      // 情况文本框
      title.value = ''
    }
    return {
      title,
      add
    }
  }
})
</script>

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

components 下的 List.vue

<template>
  <ul class="todo-main">
    <Item v-for="(todo, index) in todos" :key="todo.id" :index="index" :todo='todo' :deleteTodo="deleteTodo" :updateTodo="updateTodo" />
  </ul>
</template>

<script lang='ts'>
import { defineComponent } from 'vue'
import Item from './Item.vue'

export default defineComponent({
  name:'List',
  components: {
    Item,
  },
  props: ['todos','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>

components 下的 Item.vue

<template>
  <li @mouseenter="mouseHandler(true)" @mouseleave="mouseHandler(false)" :style="{backgroundColor:bgColor,color:myColor}">
    <label>
      <input type='checkbox' v-model='isCom'/>
      <span>{{todo.title}}</span>
    </label>
    <button class='btn btn-danger' v-show="isShow" style='display;none' @click="delTodo">删除</button>
  </li>
</template>

<script lang='ts'>
import { defineComponent,ref, computed } from 'vue'
import {Todo} from '../types/todo'

export default defineComponent({
  name:'Item',
  // props: {
  //   todo: Object as () => Todo  // 函数返回的是Todo类型
  // },
  props: {
    todo: {
      type: Object as () => Todo,  // 函数返回的是Todo类型
      required: true
    },
    deleteTodo: {
      type: Function,
      required: true
    },
    index: {
      type: Number,
      required: true
    },
    updateTodo: {
      type: Function,
      required: true
    }
  },
  data(){
    return {
      
    }
  },
  // computed: {
  //   isCom () {
  //     return this.todo.isCompleted 
  //   }
  // },
  setup(props) {
    const bgColor = ref('white')
    const myColor = ref('black')
    const isShow = ref(false)
    // 鼠标进入和离开事件的回调函数
    const mouseHandler = (flag: boolean) => {
      if(flag){
        // 鼠标进入
        bgColor.value = 'pink'
        myColor.value = 'white'
        isShow.value = true
      }else{
        // 鼠标离开
        bgColor.value = 'white'
        myColor.value = 'black'
        isShow.value = false
      }
    }
    // 删除数据的方法
    const delTodo = () => {
      if(window.confirm('确定要删除吗?')){
        props.deleteTodo(props.index)
      }
    } 
    // 计算属性方式---让当前复选框选中
    const isCom = computed({
      get(){
        return props.todo.isCompleted
      },
      set(val){
        props.updateTodo(props.todo, val)
      }
    })
    return {
      mouseHandler,
      bgColor,
      myColor,
      isShow,
      delTodo,
      isCom
    }
  }
})
</script>

<style scoped>
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;
}
</style>

components 下的 Footer.vue

<template>
  <div class="todo-footer">
    <label>
      <input type='checkbox' v-model="isCheckAll" />
    </label>
    <span><span>已完成{{count}}</span> /全部{{todos.length}} </span>
    <button class='btn btn-danger' @click="clearAllCompletedTodos">清除已完成任务</button>
  </div>
</template>

<script lang='ts'>
import { defineComponent,computed } from 'vue'
import {Todo} from '../types/todo'

export default defineComponent({
  name:'Footer',
  props: {
    todos:{
      type: Array as ()=> Todo[],
      required: true
    },
    checkAll: {
      type: Function, 
      required: true
    },
    clearAllCompletedTodos: {
      type: Function, 
      required: true
    }
  },
  setup(props){
    // 已完成的计算属性操作
    const count = computed(()=>{
      return props.todos.reduce((pre,todo,index)=>pre+(todo.isCompleted?1:0),0)
    })
    const isCheckAll = computed({
      get(){
        return count.value>0&&props.todos.length===count.value
      },
      set(val){
        props.checkAll(val)
      }
    })
    return {
      count,
      isCheckAll
    }
  }
})
</script>

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

types 下的  todo.ts

// 定义一个接口,约束state的数据类型
export interface Todo{
  id: number,
  title: string,
  isCompleted: boolean
}

utils下的  localStorageUtils.ts

import {Todo} from '../types/todo'

// 保存数据到浏览器的缓存中
export function saveTodos(todos:Todo[]){
  localStorage.setItem('todos_key',JSON.stringify(todos))
}

// 从浏览器缓存读取数据
export function readTodos():Todo[]{
  return JSON.parse(localStorage.getItem('todos_key') || '[]')
}

将 localStorageUtils.ts相关内容全部删除可正常使用!

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
对于Vue3和Vite的开发环境,需要在项目中安装Vue3和Vite相应的包。然后,通过在Vite配置文件中配置mock数据,实现TodoList数据的模拟。 首先,在项目根目录下安装Vue和Vite: ``` npm install vue@next vite --save-dev ``` 然后,在Vite的配置文件vite.config.js中配置mock数据: ```javascript module.exports = { server: { // 模拟API // 然后在api路径下,也就是在example/src/api路径下, // 根据接口路径,创建对应名称的.js文件,如/todo.js port: 3000, open: true, // 这里需要配置一下,否则会断开连接 cors: true, // 利用中间件,可以实现 mock 数据 // https://www.cnblogs.com/zero--007/p/11227108.html // 具体的编写方式可以参考上述链接 middleware: [createMockMiddleware()], }, }; ``` 其中,createMockMiddleware()方法是自定义的mock函数,示例代码如下: ```javascript const { createMockMiddleware } = require('vite-plugin-mock'); // 模拟数据 const todoData = [ { id: 1, label: "学习Vue3", done: false }, { id: 2, label: "学习Vite", done: false }, { id: 3, label: "学习TypeScript", done: true }, { id: 4, label: "整理网络知识", done: false }, ] module.exports = function () { return createMockMiddleware({ // 基于api路径,返回对应的数据 // 比如,在example/src/api/todo.js,就可以定义获取todoList数据的方法 // 比如: // export default [ // { // url: '/api/todo/list', // method: 'post', // response: () => { // return { // code: 200, // data: todoData // } // } // }, // ]; mockFiles: "src/api/*.js", }); }; ``` 在api路径下,就可以创建对应名称的js文件,如/todo.js。在该文件中,就可以定义获取todoList数据的方法,示例代码如下: ```javascript const todoData = [ { id: 1, label: "学习Vue3", done: false }, { id: 2, label: "学习Vite", done: false }, { id: 3, label: "学习TypeScript", done: true }, { id: 4, label: "整理网络知识", done: false }, ]; export default [ { url: "/api/todo/list", method: "post", response: () => { return { code: 200, data: todoData, }; }, }, ]; ``` 最后,在组件中使用api路径,获取todoList数据: ```javascript import { reactive, toRefs, onMounted } from "vue"; import axios from "axios"; export default { setup() { const state = reactive({ todoList: [], }); // 获取 todoList 数据 const getTodoList = () => { axios.post("/api/todo/list").then((res) => { state.todoList = res.data.data; }); }; // 组件加载时执行获取 todoList 数据 onMounted(() => { getTodoList(); }); // 最后需要将响应式对象转换为普通对象 return { ...toRefs(state) }; }, }; ``` 这样就可以实现Vue3和Vite环境下的TodoList数据模拟。请注意,本示例仅是提供了一个参考,实际的代码实现可能需要更加详细、复杂的设计,具体的实现方式可以参考Vue和Vite的官方文档,或者搜索相关的博客、论坛等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值