说明
- 我想要一个可以实现增删改的列表,列表中的数据包含三种状态:未完成、正在完成、已完成。
- 数据存放在浏览器缓存中。
目录结构
- src
- components:组件
- todo:输入框
- index.vue
- TodoInput.vue
- TodoItem.vue
- store
pinia 构建
// 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>
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>