ToDoList

ToDoList

使用TS+vue实现如下效果。
在这里插入图片描述

包含内容块的增删改查,主要介绍过程中的一些重要的点和思想,代码在最后。

  1. 弹窗编辑块在添加和修改时均有使用,将弹框显示的状态放在vuex中管理;
  2. 增删改查各个方法在各个组件中使用,将封装的增删改查方法实例化放入vuex中;
  3. 编辑时编辑框的内容使用深拷贝;
  4. 将编辑时id和transData过渡数据放在vuex中;
  5. 通过editId判断是添加还是编辑,添加时editId初始化为null;
  6. 通过localStoreage存储数据,方法操作基于localStoreage
  7. itemData.ts、menu.ts存放最基本的数据结构及枚举结构,dataAction.ts存放操作方法
  8. 组件menuBar,menuList, Dialog三个组件
  9. 分类查询在dataAction.ts中添加方法,更改dataList,每次读取需要重新拿到所有的数据。

itemData.ts

import Category from "./enum";
// 数据类
class ItemData {
    id!: number
    categoryId!: number
    title!: string
    content!: string
    createTime!: string
    constructor(id: number = -1, categoryId: number = -1, title: string, 
        content: string, createTime: number = -1) {
        this.id = id;
        this.categoryId = categoryId;
        this.title = title;
        this.content = content;
        this.createTime = this.dateFormate();
    }
    dateFormate(): string {
        let time = new Date();
        let str = time.getFullYear() + ":" + time.getMonth() + ":" + time.getDay();
        return str;
    }
}

export default ItemData;

dataAction.ts

import ItemData from "./itemData";
import Category from "@/store/enum";
// 数据操作
class dataAction {
    dataKey!: string
    dataList!: Array<ItemData>
    constructor(dataKey: string = "dataList") {
        this.dataKey = dataKey
        // 读取数据
        this.dataList = this.readData();
    }
    // 读取数据
    readData(): any[] {
        let strData: string | null = window.localStorage.getItem(this.dataKey);
        let arr: any[] = strData ? JSON.parse(strData) : [];
        return arr;
    }
    // 编辑数据
    editData(itemData: ItemData) {
        let editData: ItemData | undefined = this.dataList.find(item => {
            return item.id === itemData.id;
        });
        if (editData) {
            editData.categoryId = itemData.categoryId;
            editData.title = itemData.title;
            editData.content = itemData.content;
            this.saveData(this.dataList);
        }
    }
    // 删除数据
    delData(id:number): boolean {
        let index = this.dataList.findIndex(item => item.id === id);
        if (index > -1) {
            this.dataList.splice(index, 1);
            this.saveData(this.dataList);
            return true;
        }
        return false;
    }
    // 新增数据
    addData(newData: ItemData): number {
        let data = !this.dataList ? [] : this.dataList;
        let newId = !data.length ? 1 : data[data.length - 1].id + 1;
        newData.id = newId;
        data.push(newData);
        // 保存数据
        this.saveData(data);
        return newId;
    }
    saveData(strData: ItemData[]): void {
        let str: string = JSON.stringify(strData);
        window.localStorage.setItem(this.dataKey, str);
    }
    // 拿到类型
    getCategory(cateId: Category): string {
        const arrNames = ["生活", "学习", "娱乐"];
        return arrNames[cateId];
    }
    // 分类查询
    getTypeData(cateId: Category): Array<ItemData> {
        this.dataList = this.readData();
        cateId = Number(cateId);
        if (cateId !== 3) {
            this.dataList = this.dataList.filter(item => {
                return item.categoryId === cateId;
            });
        }
        return this.dataList;
    }
}
export default dataAction;

store.ts

import Vue from 'vue';
import Vuex from 'vuex';
import dataAction from '@/store/dataAction';
Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    showDialog: false,
    dataAction: new dataAction(),
    transData: null,
    editId: null
  },
  mutations: {
    showEditData(state: any, editData: any) {
      state.transData = editData;
    },
    
  },
  actions: {

  },
});

enum.ts

enum Category {
    Work = 0,
    Life = 1,
    Study = 2,
    All = 3
}
export default Category;

组件
home.vue

<template>
  <div class="home">
    <MenuBar />
    <MenuList />
    <Dialog />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import Dialog from "@/components/Dialog.vue";
import MenuList from "@/components/MenuList.vue";
import MenuBar from "@/components/MenuBar.vue";

@Component({
  components: {
    Dialog,
    MenuBar,
    MenuList,
  },
})
export default class Home extends Vue {}
</script>

menubar.vue

<template>
    <div class="menu-bar">
        <div class="logo">To Do List</div>
        <div class="operate">
            <button @click="add">添加</button>
            <select v-model="type">
                <option value = 3>全部</option>
                <option value = 0>生活</option>
                <option value = 1>学习</option>
                <option value = 2>娱乐</option>
            </select>
        </div>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator';

@Component({
    components: {}
})
export default class MenuBar extends Vue{
    type: number = 3
    @Watch("type") selectData(type) {
        console.log(this.type);
        this.$store.state.dataAction.getTypeData(this.type);
    }
    add(): void {
        this.$store.state.editId = null;
        this.$store.state.showDialog = true;
    }
}
</script>
<style lang="scss">
.menu-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 50px;
    width: 100%;
}
</style>

menuList.vue

<template>
    <div class="menu-list">
        <Block v-for="item in dataList" :key="item.id" :val="item" />
    </div>
</template>
<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator';
import Block from "@/components/Block.vue";
import ItemData from "@/store/ItemData.ts";
@Component({
    components: { Block }
})
export default class MenuList extends Vue{
    dataList: Array<ItemData> = this.$store.state.dataAction.dataList;
    @Watch("$store.state.dataAction.dataList") getData() {
        this.dataList = this.$store.state.dataAction.dataList;
    }
}
</script>
<style lang="scss">
.menu-list {
    display: flex;
    flex-wrap: wrap;
    padding: 20px;
    height: calc(100% - 50px);
    box-sizing: border-box;
}
</style>

Block.vue

<template>
<div class="block">
    <div class="head">
        <div class="left">
            <input class="title" v-model="val.title" />
        </div>
        <div class="right">
            <button @click="edit(val.id)">编辑</button>
            <button @click="del(val.id, val.title)">删除</button>
        </div>
    </div>
    <div class="next">
        <div class="left">
            <span>{{val.createTime}}</span>
        </div>
        <div class="right">
            <span>{{getCateName(val.categoryId)}}</span>
        </div>
    </div>
    <div class="content">
        <!-- <span>{{val.content}}</span> -->
        <textarea v-model="val.content"></textarea>
    </div>
</div>  
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
import {ItemData} from "@/store/itemData.ts";
@Component({
    components: {}
})
export default class Block extends Vue{
    @Prop(ItemData) val
    getCateName(id: number): string {
        return this.$store.state.dataAction.getCategory(id);
    }
    del(id: number, title: string) {
        if (window.confirm(`确定删除${title}的内容吗?`)) {
            this.$store.state.dataAction.delData(id);
        }
    }
    edit(id: number) {
        // 展示模态框
        this.$store.state.showDialog = true;
        this.$store.state.editId = id;
        let item = this.$store.state.dataAction.dataList.find((item: any) => {
            return id === item.id;
        });
        // 深拷贝
        item = JSON.parse(JSON.stringify(item));
        this.$store.commit("showEditData", item);
    }
}
</script>
<style lang="scss">
.block {
    flex: 0 1 auto;
    width: 25%;
    margin-right: 20px;
    .head, .next {
        display: flex;
        justify-content: space-between;
        align-items: center;
        .left {
            flex: auto;
            input {
                width: 100%;
            }
        }
        .right {
        }
    }
    .next {
        .left {
            text-align: left;
        }
    }
    .content {
        textarea {
            width: 100%;
            min-height: 100px;
        }
    }
}
</style>

dialog.vue

<template>
  <div class="dialog" v-if="$store.state.showDialog">
    <div class="form">
      <div class="block">
        <div class="head">
          <div class="left">
              <input class="title" v-model="form.title" />
          </div>
          <div class="right">
              <select v-model="form.categoryId">
                <option value = 0>生活</option>
                <option value = 1>学习</option>
                <option value = 2>娱乐</option>
              </select>
              <button @click="save">保存</button>
              <button @click="cancle">取消</button>
          </div>
        </div>
        <div class="content">
            <textarea v-model="form.content">内容</textarea>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import Block from "@/components/Block.vue";
import itemData from "@/store/itemData.ts";
import category from  "@/store/enum.ts";
import ItemData from '@/store/itemData.ts';

@Component({
    components: { Block }
})
export default class Dialog extends Vue {
  form: ItemData = new ItemData(-1,0);
  created() {
    if(this.$store.state.editId) {
      this.form = this.$store.state.transData;
    }
  }
  @Watch("$store.state.editId") editId() {
    this.form = new ItemData(-1,0);
    if(this.$store.state.editId) {
      this.form = this.$store.state.transData;
    }
  }
  save() {
    if(this.form && this.form.categoryId > -1 && this.form.content.trim().length > 0 && this.form.title.trim().length > 0 ) {
      this.form.categoryId = Number(this.form.categoryId);
      if(!this.$store.state.editId) {
        this.$store.state.dataAction.addData(this.form);
      } else {
        this.$store.state.dataAction.editData(this.form);
      }
      this.$store.state.showDialog = false;
    } else {
      window.alert("输入未完成");
    }
  }
  cancle() {
    this.$store.state.showDialog = false;
  }
}
</script>
<style lang="scss" scoped>
.dialog {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0, 0.3);
  .form {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    .block {
      .head {
        display: flex;
        justify-content: space-between;
        align-items: center;
        .left {
            flex: auto;
            margin-right: 10px;
            input {
                width: 100%;
            }
        }
        .right {
        }
      }
      .content {
          textarea {
              width: 100%;
              min-height: 100px;
          }
      }
    }
  }
}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值