简单的todolist项目

170 阅读3分钟

项目准备

使用vite创建并且初始化项目

npm create vite

如果没有安装vite需要先安装vite

npm install vite

然后再在终端执行第一个命令使用vite创建项目 执行npm create vite后会出现以下选项

// 项目名称
√ Project name: ... todolist
// 选择框架,这里选vue
? Select a framework: » - Use arrow-keys. Return to submit.
    Vanilla
>   Vue
    React
    Preact
    Lit
    Svelte
    Others
// 选择JavaScript,你也可以选择TypeScript,推荐选择TS
? Select a variant: » - Use arrow-keys. Return to submit.
>   JavaScript
    TypeScript
    Customize with create-vue ↗
    Nuxt ↗

然后在终端中分别键入以下命令

// 进入项目文件夹
cd todolist
// 安装vite默认的依赖
npm install
// 打开本地服务器
npm run dev
然后你会看到下面这个选项
  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help
点击http://localhost:5173/就能在浏览器中看到项目了

安装项目所需要的依赖(dependencies)

npm install tailwindcss postcss autoprefixer -D

编写配置文件

// 在项目根目录下找到vite.config.js,如果没有就自己创建一个
// 在vite.config.js进行配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import autoprefixer from 'autoprefixer'
import tailwindcss from 'tailwindcss'

export default defineConfig({
    // 服务器配置,可以忽略
    server:{
        // 自动打开浏览器
        open:true;
        // 更改端口号,vue默认本地服务器端口号是5173
        port:5000;
    },
    // css配置
    css:{
        // PostCSS是一个灵活的CSS处理工具,可以通过插件实现语法转换、前缀添加等操作。
        postcss:{
            plugins:[
                // Autoprefixer可以为CSS属性自动添加浏览器厂商前缀。
                autoprefixer({
                    // 浏览器版本设置
                     overrideBrowserslist: [
                      "last 2 versions",
                      "ie >= 9",
                    ]
                }),
                // tailwindcss配置,这里的配置比较简单,如果有更复杂的配置建议建立一个
                // 单独的tailwind.config.js配置文件俩编写配置信息
                tailwindcss({
                    content:['./index.html',''./src/**/*.vue]
                })
            ]
        }
    }
    
})

删除默认文件

  1. HelloWorld.vue
  2. 清空APP.vue,添加如下代码
<template></template>
<script>
// 其他代码
</script>
  1. 清空stlye.css文件,添加如下代码
@tailwind base;
@tailwind componets;
@tailwind utilities;
/* 导入reset样式 */
@layer base{
*,
*::before,
*::after {
    box-sizing: border-box
}

body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
    margin: 0
}

ul[role="list"],
ol[role="list"] {
    list-style: none
}

html:focus-within {
    scroll-behavior: smooth
}

body {
    min-height: 100vh;
    text-rendering: optimizeSpeed;
    line-height: 1.5
}

a:not([class]) {
    text-decoration-skip-ink: auto
}

img,
picture {
    max-width: 100%;
    display: block
}

input,
button,
textarea,
select {
    font: inherit
}

@media(prefers-reduced-motion:reduce) {
    html:focus-within {
        scroll-behavior: auto
    }

    *,
    *::before,
    *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important
    }
}
}

编写组件

  1. components文件夹中创建两个SFC文件:Search.vue,List.vue,键入以下代码
<template>
<!-- 其他html代码-->
</template>
<script>
export default{
    // 其他代码
}
</script>
  1. 编辑App.vue
<template>
<div class="todoWrapper">
  <div class="todoheader">
   <h5>Todo List</h5>
    <div class="add">
      <input 
      placeholder="some todo"
      type="text">
      <button>Add</button>
    </div>
  </div>
  <!-- 自定义组件 -->
  <Search/>
  <List/>
  <div class="todoheader">
    <span>DoneTodo:</span>
    <span>Todo:</span>
  </div>
</div>
</template>
<script>
// 引入需要List和Search组件
import List from './components/List.vue'
import Search from './components/Search.vue'
</script>

在浏览器网页中你会看到 image.png

添加适当的样式

<div class="todoWrapper w-full flex flex-col items-center">
  <div class="todoheader w-full flex flex-col items-center">
    <h5 class="text-3xl mt-4 mb-4 text-slate-300">Todo List</h5>
    <div class="add w-1/2">
      <input 
      class="w-3/4 pl-2 py-0 border-0 rounded h-8 text-xl"
      placeholder="some todo"
      type="text">
      <button
      class="ml-2 h-8 py-0 px-2 text-xl rounded-xl border-0"
      >Add</button>
    </div>
  </div>
  <!-- 自定义组件 -->s
  <Search/>
  <List/>
  <div class="todoheader w-1/2 flex flex-col">
    <span>DoneTodo:</span>
    <span>Todo:</span>
  </div>
</div>

添加样式后的todolist

image.png 3. add Todo

// App.vue <script>标签中添加如下代码
 data() {
    // 添加子组件
    components:{Search,List}
    return {
      todos: [],
      searchValue: '',
      todo: ''
    }
  },
  mounted() {
    this.todos = localStorage.getItem('todos')
      ? JSON.parse(localStorage.getItem('todos'))
      : []
  },
  methods: {
     // 将todo存储到localStorage中
    saveLocalStorage() {
      localStorage.setItem('todos', JSON.stringify(this.todos))
    },
    // 添加todo
    addTodo() {
      // 每一个todo有两个属性:todo内容以及todo是否完成
      this.todos.unshift({
        activity: this.todo,
        isDone: false
      })
      // 更新locaStorage中的todos数组
      this.saveLocalStorage()
      this.todo = ''
    }
  }
  // App.vue <template>标签中修改如下代码
   <input v-model="todo" @keyup.enter="addTodo" class="w-3/4 pl-2 py-0 border-0 rounded h-8 text-xl" placeholder="some todo" type="text">
        <button @click="addTodo" class="ml-2 h-8 py-0 px-2 text-xl rounded-xl border-0">Add</button>

localStorage中的todos数组

image.png image.png 4. 编写List.vue组件,显示所有todo

<template>
<div class="list">
    <ul>
        <li v-for="(todo,index) in todos" :key="index">
            {{ todo.activity }}
            <button>{{ todo.isDone }}</button>
            <button>Delete</button>
        </li>
    </ul>
</div>
</template>
<script>
export default{
    props:{
        todos:{
            type:Array,
            default:[]
        }
    }
}
</script>
// 修改App.vue中的List组件
// 这里用到了props进行组件之间的信息传递
    <List :todos="todos"/>

image.png 修改list样式

    <div class="list w-full mt-2">
    <ul class="w-full flex flex-col items-center">
        <li 
        v-for="(todo,index) in todos" 
        :key="index"
        class="w-1/2 flex flex-row justify-start">
            <span class="w-3/4 bg-violet-200 mb-1 pl-2 py-1 rounded">{{ todo.activity }}</span>
            <div class="ml-2 mb-1">
                <button  class="mr-2 p-1 rounded bg-cyan-500">{{ todo.isDone }}</button>
                <button 
                class=" bg-cyan-500 p-1 rounded">Delete</button>
            </div>
        </li>
    </ul>
</div>

修改后todolist的样式

image.png 在App.vue添加函数

  computed:{
    doneTodo(){
      return this.todos.filter(item=>item.isDone).length
    },
    stillTodo(){
      return this.todos.filter(item=>!item.isDone).length
    }
  },
  // 修改<template>内的代码
    <span>DoneTodo:{{ doneTodo }}</span>
    <span>Todo:{{ stillTodo }}</span>

image.png 5. 添加删除todo功能

// App.vue的export default 的对象中的methods添加如下代码
methods:{
deleteTodo(delteIndex){
      this.todos=this.todos.filter((item,index)=>{
        if(index!==delteIndex) return item
      })
      this.saveLocalStorage()
    }
}
// 修改App.vue中的List组件
<List :todos="todos" @delete-todo="deleteTodo"/>
// List添加如下代码
methods:{
        deleteTodo(index){
        // this.$emit也是组件间进行通信的一种方式,通过发出自定义事件进行通信
            this.$emit('deleteTodo',index)
        }
    }
// list <template>中进行如下修改
<button 
                @click="deleteTodo(index)"
                class=" bg-cyan-500 p-1 rounded">Delete</button>

现在点击delete就可以删除todo啦

  1. 编写Search组件
<template>
<div class="search my-2 w-1/2 flex flex-col-reverse justify-end">
    <input
    v-model.trim="search" 
    @keyup="searchTodo(search)"
    class="w-1/2 ml-80 rounded pl-2" type="text" placeholder="Search your todo">
</div>
</template>

<script>
export default{
    data(){
        return{
            search:''
        }
    },
    methods:{
        searchTodo(search){
            search = search.toLowerCase()||search
            this.$emit('searchTodo',search)
        }
    }
}
</script>
// App.vue中添加如下代码
// computed中添加
displayTodo(){
      const display = this.todos.filter(item=>
        item.activity.includes(this.searchValue)||item.activity.toLowerCase().includes(this.searchValue)
      )
      return display
    }
// methods中添加
searchTodo(searchVal){
      this.searchValue = searchVal
    }
// 修改App.vue中的search组件
<Search @search-todo="searchTodo"/>
  1. 添加功能,todo完成则DONE按钮发生变化
// List.vue中的 methods添加代码
 doneTodo(index){
            this.$emit('doneTodo',index)
            const donebutton= this.$refs[`donebutton-${index}`][0]
            if(this.todos[index].isDone){
                donebutton.classList.add('line-through')
            }else if(donebutton.classList.contains('line-through')){
                donebutton.classList.remove('line-through')
            }
        // 修改List.vue <template>中的代码
         <button  
                :ref="`donebutton-${index}`"
                @click="doneTodo(index)"
                class="mr-2 p-1 rounded bg-cyan-500">DONE</button>
// App.vue methods添加如下代码       
doneTODO(doneIndex){
      this.todos = this.todos.filter((item,index)=>{
        if(index===doneIndex) item.isDone=!item.isDone
        return item
      })
    }
    // 修改App.vue的List组件
    <List :todos="displayTodo" @delete-todo="deleteTodo" @done-todo="doneTODO"/>

效果展示 点击DONE,DONE中间会有一条横线

image.png

技术栈

vue + vite + tailwindcss