Featured image of post Vue3 pinia 增删改列表 2023

Vue3 pinia 增删改列表 2023

说明

  • 我想要一个可以实现增删改的列表,列表中的数据包含三种状态:未完成、正在完成、已完成。
  • 数据存放在浏览器缓存中。

2023-02-17-vue_list

目录结构

  • src
    • components:组件
      • todo:输入框
        • index.vue
        • TodoInput.vue
        • TodoItem.vue
    • store
      • modules
        • todo
          • index.ts
          • types.ts
      • index.ts

pinia 构建

  • src/store/index.ts
// src/store/index.ts
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

import { useTodoStore } from "./modules/todo";
import { TodoState } from "./modules/todo/types";

export { useTodoStore };
export type { TodoState };
export default pinia;

// 1. 导入 store
// import { useMainStore } from '@/store'

// 2. 获取 state
// const mainStore = useMainStore()
// 获取数据三种办法

// <todo-list :todo-list="todoStore.$state.TodoList" />

// mainStore.title

// mainStore.$patch(state => {
//   state.count++
// })

// 用来监听数据变化, 详情看文档
// mainStore.$subscribe((mutation, state) => {})

// 3. 解构导出方法
// const { } = useMainStore()

// 补: 如需将方法传递给子组件,需要使用 defineExpose 导出
// defineExpose({})

// 4.使用 Action/Getter
// 直接调用即可
// emit('ActionName', data)
  • src/store/modules/todo/types.ts
// src/store/modules/todo/types.ts
export interface TodoState {
  title: string;
  TodoList: ITodo[];
}

export interface ITodo {
  id: number;
  content: string;
  status: TODO_STATUS;
}

export enum TODO_STATUS {
  WILL = "will",
  DOING = "doing",
  FINISHED = "finished",
}
  • src/store/modules/todo/index.ts
import { defineStore } from "pinia";
import piniaStore from "@/store/index";
import { TODO_STATUS } from "./types";
import type { TodoState, ITodo } from "./types";

export const useTodoStore = defineStore("todo", {
  state: (): TodoState => ({
    title: "Todo List Title",
    TodoList: [] as ITodo[],
  }),
  getters: {
    getTodo(state: TodoState): TodoState {
      return { ...state };
    },
  },
  actions: {
    // 设置
    // setTodo(partial: Partial<TodoState>) {
    //   this.$patch(partial)
    // },
    // 重置
    // resetTodo() {
    //   this.$reset()
    // },

    addTodoItem(todoContent: string): void {
      const todo: ITodo = {
        id: new Date().getTime(),
        content: todoContent,
        status: TODO_STATUS.WILL,
      };
      this.TodoList.unshift(todo);
    },
    changeTodoItem(todo: { id: number; content: string }): void {
      this.TodoList = this.TodoList.map((item: ITodo) => {
        if (item.id === todo.id) {
          item.content = todo.content;
        }
        return item;
      });
    },
    removeTodoItem(id: number): void {
      this.TodoList = this.TodoList.filter((item: ITodo) => item.id !== id);
    },
    setTodoStatus(id: number): void {
      this.TodoList = this.TodoList.map((item: ITodo) => {
        if (item.id === id) {
          item.status =
            item.status === TODO_STATUS.FINISHED
              ? TODO_STATUS.WILL
              : TODO_STATUS.FINISHED;
        }
        return item;
      });
    },
    // 确保只存在一个<正在完成>
    setDoingStatus(id: number): void {
      this.TodoList = this.TodoList.map((item: ITodo) => {
        if (item.id !== id) {
          if (item.status === TODO_STATUS.DOING) {
            item.status = TODO_STATUS.WILL;
          }
        } else {
          item.status =
            item.status === TODO_STATUS.WILL
              ? TODO_STATUS.DOING
              : TODO_STATUS.WILL;
        }

        return item;
      });
    },
  },
  persist: true,
  // persist: {
  //   // storage: sessionStorage,
  //   storage: localStorage,
  //   paths: ['TodoList'],
  // },
});

export function useTodoOutsideStore() {
  return useTodoStore(piniaStore);
}

组件构建

TodoItem

  • src\components\Todo\TodoItem.vue
<template>
  <div class="todo-item">
    <div class="status-input">
      <input
        type="checkbox"
        :checked="item.status === 'finished'"
        @click="setStatus(item.id)"
      />
    </div>
    <div
      v-if="changeStatus === false"
      class="text-connect"
      @click=";(changeStatus = true), (changeItemContent = item.content)"
    >
      <span :class="item.status === 'finished' ? 'line-through' : ''">
        {{ item.content }}
      </span>
    </div>
    <div v-else class="change-status">
      <input v-model="changeItemContent" type="text" />
      <button @click="changeContent(item.id, item.content)">修改</button>
    </div>
    <div class="status-action">
      <button @click="removeTodo(item.id)">删除</button>
      <button
        v-if="item.status !== 'finished'"
        :class="item.status === 'doing' ? 'doing' : 'will'"
        @click="setDoing(item.id)"
      >
        {{ item.status === 'doing' ? '正在做...' : '马上做' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { PropType, ref } from "vue";
  import { ITodo } from "@/store/modules/todo/types";

  const props = defineProps({
    item: {
      type: Object as PropType<ITodo>,
      required: true,
    },
  });

  // 移除,设置状态
  const emit = defineEmits([
    "removeTodo",
    "setStatus",
    "setDoing",
    "changeTodoItem",
  ]);

  const removeTodo = (id: number): void => {
    emit("removeTodo", id);
  };
  const setStatus = (id: number): void => {
    emit("setStatus", id);
  };
  const setDoing = (id: number): void => {
    emit("setDoing", id);
  };

  // 修改数据
  const changeStatus = ref<boolean>(false);
  const changeItemContent = ref<string>("");
  const changeContent = (id: number, content: string) => {
    if (changeItemContent.value !== content) {
      emit("changeTodoItem", { id, content: changeItemContent.value });
    }
    changeStatus.value = false;
  };
</script>

<style lang="scss">
  .todo-item {
    width: 100%;
    height: 30px;
    line-height: 30px;
    span,
    button {
      margin-left: 5px;
    }
    .status-input {
      display: inline-block;
      float: left;
      input {
        width: 15px;
        height: 15px;
      }
    }
    .text-connect {
      display: inline-block;
      width: 150px;
    }
    .change-status {
      display: inline-block;
      margin-left: 5px;
      input {
        width: 120px;
        height: 15px;
      }
    }
    .status-action {
      display: inline-block;
      float: right;
      margin-right: 10px;
      height: 30px;
    }

    .line-through {
      text-decoration: line-through;
    }

    .doing {
      background-color: #ededed;
      color: #999;
    }

    .willdo {
      background-color: orange;
      color: #fff;
    }
  }
</style>

TodoInput

  • src\components\Todo\TodoItem.vue
<template>
  <div class="todo-input">
    <input v-model="todoValue" type="text" @keyup.enter="addTodoValue" />
    <button @click="addTodoValue">确认</button>
  </div>
</template>

<script setup lang="ts">
  import { ref } from "vue";

  // 保存输入框的值
  const todoValue = ref<string>("");

  const emit = defineEmits(["addTodo"]);

  const addTodoValue = (): void => {
    // 判断是否为空
    if (todoValue.value.trim().length) {
      emit("addTodo", todoValue.value);
      todoValue.value = "";
    }
  };
</script>

<style lang="scss">
  .todo-input {
    input {
      display: inline-block;
      outline: none;
      height: 20px;
      width: 71%;
    }
    button {
      display: inline-block;
      margin-left: 5px;
    }
  }
</style>

index

  • src\components\Todo\index.vue
<template>
  <div class="wrapper">
    点击文字可修改内容。
    <todo-input class="todo-input" @add-todo="addTodoItem" />
    <todo-item
      v-for="item of todoList"
      :key="item.id"
      class="todo-item"
      :item="item"
      @change-todo-item="changeTodoItem"
      @remove-todo="removeTodoItem"
      @set-status="setTodoStatus"
      @set-doing="setDoingStatus"
    />
  </div>
</template>

<script setup lang="ts">
  import { useTodoStore } from "@/store";
  import { ITodo } from "@/store/modules/todo/types";

  import TodoItem from "@/components/todo/TodoItem.vue";
  import TodoInput from "@/components/todo/TodoInput.vue";

  const props = defineProps({
    todoList: {
      type: Array<ITodo>,
      required: true,
    },
  });

  // 向 TodoItem组件 传递方法,用来对状态的操作
  const {
    setDoingStatus,
    setTodoStatus,
    removeTodoItem,
    changeTodoItem,
    addTodoItem,
  } = useTodoStore();
  defineExpose({
    setDoingStatus,
    setTodoStatus,
    removeTodoItem,
    changeTodoItem,
    addTodoItem,
  });
</script>

<style lang="scss">
  .wrapper {
    border: 1px red solid;
    width: 300px;
    .todo-input {
      margin: 5px;
    }
    .todo-item {
      padding: 0px 5px;
    }
  }
</style>
Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
发表了53篇文章 · 总计28.17k字
使用 Hugo 构建
主题 StackJimmy 设计