vue3+ts制作一个简易的pc便签

在这里插入图片描述
在这里插入图片描述

App.vue

<template>
  <!-- <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view/> -->
  <div id="root">
    <div className="todo-container">
      <div className="todo-wrap">
        <Header :addtodo="addtodo"/>
        <List :todos="state.todos" :deletetodo="deletetodo" :updatetodo="updatetodo"/>
        <Footer :todos="state.todos" :chickall="chickall" :clearall="clearall"/>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, reactive, watch } from "vue";
import Footer from "./components/footer/footer.vue";
import Header from "./components/header/header.vue";
import List from "./components/list/list.vue";
//导入接口类型
import {todo} from "./type/todo"
//导入缓存文件
import {savetodos,readtodos} from "./until/localStorageUtils"
export default defineComponent({
  components: { List, Header, Footer },
  setup() {
    
    const state = reactive<{todos:todo []}>({
      todos: [
        { id: 1, title: "吃饭", isture: true },
        { id: 2, title: "睡觉", isture: false },
        { id: 3, title: "打游戏", isture: false },
        { id: 4, title: "打代码", isture: true },
      ],
    });
    //定义一个方法用来接收输入框里面传来的数据并把它加到state.todos里面面
    const addtodo=(todo:todo)=>{
      state.todos.unshift(todo)
    }
      console.log(state.todos);
   //定义一个改变istrue的方法
    const updatetodo=(todo:todo,is:boolean)=>{
         todo.isture=is
    }
    //定义一个删除的方法
    const deletetodo=(index:number)=>{
      state.todos.splice(index,1)
    }
   //定义全选全不选的方法
   const chickall=(iss:boolean)=>{
    state.todos.forEach((a)=>{
       a.isture= iss
    })
   }
   //定义清楚选中的方法
  const clearall=()=>{
    state.todos= state.todos.filter(a=>!a.isture)
  }
  watch(()=>state.todos,savetodos,{deep:true})
  onMounted(()=>{
    setTimeout(()=>{
    state.todos=readtodos()
    },1000)
  })
    return {
      state,
      addtodo,
      deletetodo,
      updatetodo,
      chickall,
      clearall
    };
  },
});
</script>

<style>
@import "./App.css";
</style>

App.css

.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: #ecb372;
    border: none;
  }
  
  .btn-danger:hover {
    color: #fff;
    /* background-color: #ffbd06; */
    opacity: 1 !important;
    transform: scale(1.1,1.1);

  }
  
  .btn:focus {
    outline: none;
  }
  
  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    margin-top: 100px;
    padding: 10px;
    border: 2px solid rgb(255, 255, 255);
    border-radius: 5px;
    /* background-color:rgb(250, 232, 235); */
    color: rgb(8, 8, 8);
  }
  body{
    background-color: rgb(253, 210, 217);
    background-size: 100% 200%;
    
  }

footer.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" v-model="footall" />全选
    </label>
    <span>
      <span>已完{{count}} </span> / 全部{{todos.length}}
    </span>
    <button  class="clearsllbtn" @click="clearall" >清除已完成任务</button>
  </div>
</template>
<style scoped>
@import"./footer.css";
</style>
<script lang="ts">
//导入接口类型
import { todo } from "../../type/todo";
import { computed, defineComponent } from "vue";

export default defineComponent({
  props: {
    todos: {
      type: Array as () => todo[],
      required:true
    },
    chickall:{
       type: Function,
      required:true
    },
    clearall:{
      type:Function,
      required:true
    }
  },
  setup(props) {
    const count = computed(() => {
      return props.todos.reduce(
        (pre, todo, index) => pre + (todo.isture ? 1 : 0),
        0
      );
    });
    const footall=computed({
      get(){
         return count.value>0&&props.todos.length===count.value
      } ,
      set(val){
       props.chickall(val)
      }
    })
    return {
      count,
      footall
    };
  },
});
</script>

footer.css


  /*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;
  }
  .clearsllbtn{
 margin-left:200px;
 border:none;
 border-radius: 13px;
  }
  .clearsllbtn:hover{
    background-color: palevioletred;
  }

header.vue

<template>
  <div class="todo-header">
    <input
      type="text"
      placeholder="请输入你的任务名称,按回车键确认"
      v-model="title"
      @keyup.enter="add"
    />
  </div>
</template>
<style scoped>
@import "./header.css";
</style>
<script lang="ts">
import { defineComponent, ref } from "vue";
//导入接口类型
import { todo } from "../../type/todo";
export default defineComponent({
  props: {
    addtodo: {
      type: Function,
      required: true,
    },
  },
  setup(props) {
    const title = ref("");

    const add = () => {
      if (title.value == "") {
        alert("请输入内容");
        return
      }
      const todo: todo = {
        id: Date.now(),
        title: title.value,
        isture: false,
      };
      props.addtodo(todo);
      title.value=''
    };

    return {
      add,
      title,
    };
  },
});
</script>

header.css

/*header*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
    opacity: 0.7;
  }
  
  .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);
  }

list.vue

<template>
  <ul className="todo-main">
    <Listitem
      v-for="(todos,index) in todos"
      :key="todos.id"
      :todos='todos'
      :deletetodo="deletetodo"
      :index="index"
      :updatetodo="updatetodo"
    ></Listitem>
  </ul>
</template>
<style scoped>
@import "./list.css";
</style>
<script lang="ts">
import Listitem from "./listitem/listitem.vue";
import { defineComponent } from "vue";

export default defineComponent({
  components: {
    Listitem,
  },
  props: {
    todos: {
      type: Object,
      required: true,
    },
    deletetodo: {
      type: Function,
    },
    updatetodo:{
      type:Function,
      requirtd:true
    }
  },
  setup() {},
});
</script>

list.css

/*main*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
    background-color: white ;
    opacity: 0.7;
  }
  
  .todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
  }

listitem.vue

<template>

  <li class="itli">
    <label>
      <input
        type="checkbox"
        v-model="isture"
      />
      <span>{{todos.title}}</span>
    </label>
    <button
      class="btn btn-danger"
      @click="deletetodoitem"
    >删除</button>
  </li>

</template>
<style scoped>
@import "./listitem.css";
</style>
<script lang="ts">
import { computed, defineComponent, } from "vue";
//导入接口类型
import { todo } from "../../../type/todo";
export default defineComponent({
  props: {
    //定义todos的类型是自己定义的todo接口
    todos: {
      type:Object as () => todo,
      required: true,
    },
    index: Number,
    deletetodo: {
      type: Function,
      required: true,
    },
    updatetodo: {
      type: Function,
      required: true,
    },
  },
  setup(props) {
    //定义一个点击事件把每个item的index传过去
    const deletetodoitem = () => {
      // alert("asdsa")
      // console.log(props.todos);
      if (window.confirm("确定删除吗,亲~")) {
        props.deletetodo(props.index);
        // alert(props.deletetodo)
      }
    };
    //定义一个计算属性来把isture传过去让app.vue来控制
    const isture =computed({
      get(){
      return props.todos.isture
      },
      set(val){
            props.updatetodo(props.todos,val)
      } 
    })
    
    return {
      deletetodoitem,
      isture,
    };
  },
});
</script>

listitem.css


  /*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;
    opacity: 1;
    margin-right: 10px;
  }
  
  li:before {
    content: initial;
  }
  
  li:last-child {
    border-bottom: none;
  }
  .itli{
    width: 100%;
  }
  .itli:hover {
   width: 98.5%;
      background-color: palevioletred;
  }
  .itli:hover button{
    display: block;
    
  }
  .btn{
    display: none;
  }
  .btn-danger{
    display: none;
  }

缓存部分
localStorageUtils.ts

import { todo } from "@/type/todo";
//保存数据到浏览器中
export function savetodos(todos:todo []){
    localStorage.setItem('todo_key',JSON.stringify(todos))
}
//从浏览器的缓存中读取数据
export function readtodos():todo []{
    return JSON.parse(localStorage.getItem('todo_key')||'[]')
}
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万事胜意sy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值