1、Todos.vue
<template>
<div>
<!-- 新增todo -->
<!-- v-model绑定的值是todo-title -->
<EditTodo
v-model:todo-title="newTodo"
@keyup.enter="addTodo"
autofocus
placeholder="新增今日待办"
autocomplete="off"
>
</EditTodo>
<!-- todo列表 -->
<ul>
<TodoItem
v-for="todo in filtedTodos"
:key="todo.id"
:todo="todo"
v-model:edited-todo="editedTodo"
@remove-todo="removeTodo"
></TodoItem>
</ul>
<!-- 过滤 -->
<Filter :items="filterItems" v-model="visibility"></Filter>
</div>
</template>
<script>
import { reactive, toRefs, computed, watchEffect } from "vue";
import TodoItem from './TodoItem.vue'
import Filter from './Filter.vue'
// 过滤器
const filters = {
all(todos) {
return todos;
},
active(todos) {
return todos.filter((todo) => !todo.completed);
},
completed(todos) {
return todos.filter((todo) => todo.completed);
},
};
// 缓存操作
const todoStorage = {
fetch() {
let todos = JSON.parse(localStorage.getItem("vue3-todos") || "[]");
todos.forEach((todo, index) => {
todo.id = index + 1;
});
return todos;
},
save(todos) {
localStorage.setItem("vue3-todos", JSON.stringify(todos));
},
};
export default {
components:{
TodoItem,
Filter
},
setup() {
const state = reactive({
newTodo: "",
todos: todoStorage.fetch(), //获取是否有缓存数据
editedTodo: null, //正在编辑的todo
filterItems: [
{
label:'All',value:'all'
},
{
label:'Active',value:'active'
},
{
label:'Completed',value:'completed'
},
],
visibility: "all",
filtedTodos: computed(() => {
return filters[state.visibility](state.todos);
}),
});
function addTodo() {
state.todos.push({
id: state.todos.length + 1,
title: state.newTodo,
completed: false,
});
state.newTodo = "";
}
function removeTodo(todo) {
state.todos.splice(state.todos.indexOf(todo), 1);
}
watchEffect(() => {
// 执行一些操作,其中必须含有响应式变量(state.todos是响应式变量)
todoStorage.save(state.todos);
});
return {
...toRefs(state),
addTodo,
removeTodo,
};
},
};
</script>
<style scoped>
</style>
2、EditTodo.vue
<template>
<!-- v-bind="$attrs" 承接所有的props -->
<input
type="text"
:value="todoTitle"
@input="onInputChange"
v-bind="$attrs"
/>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
emits: {
onInputChange: null,//click事件没有检验
},
props: {
todoTitle: {
type: String,
default: "",
},
},
setup(props,{emit}) {
const state = reactive({
count: 0,
});
function onInputChange(e){
emit('update:todoTitle',e.target.value)
}
return {
...toRefs(state),
onInputChange,
};
},
};
</script>
<style lang="scss" scoped></style>
3、TodoItem.vue
<template>
<li :class="{ completed: todo.completed, editing: todo === editedTodo }">
<!-- 绑定完成状态 -->
<div class="view">
<input type="checkbox" v-model="todo.completed" />
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button @click="removeTodo(todo)">X</button>
</div>
<!-- 编辑待办 -->
<EditTodo
class="edit"
v-model:todo-title="todo.title"
v-todo-focus="todo === editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)"
>
</EditTodo>
</li>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
props: {
todo: {
type: Object,
required: true,
},
editedTodo: {
type: Object,
},
},
emits:["remove-todo","update:edited-todo"],
setup(props, { emit }) {
const state = reactive({
beforeEiditCache: "", // 缓存编辑前的title
});
function removeTodo(todo) {
emit("remove-todo", todo);
}
function editTodo(todo) {
state.beforeEiditCache = todo.title;
emit("update:edited-todo", todo);
}
function cancelEdit(todo) {
todo.title = state.beforeEiditCache;
emit("update:edited-todo", null);
}
function doneEdit(todo) {
emit("update:edited-todo", null);
}
return {
...toRefs(state),
editTodo,
cancelEdit,
doneEdit,
removeTodo,
};
},
// 自定义指令
directives: {
"todo-focus": (el, { value }) => {
if (value) {
el.focus();
}
},
},
};
</script>
<style scoped>
.completed label {
text-decoration: line-through;
}
.edit,
.editing .view {
display: none;
}
.view,
.editing .edit {
display: block;
}
</style>
4、Filter.vue
<template>
<p class="filters">
<span
v-for="item in items"
:key="item.value"
@click="$emit('update:modelValue', item.value)"
:class="{ seleted: modelValue === item.value }"
>{{ item.label }}</span
>
</p>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
props: ["items", "modelValue"],
emits: ["update:modelValue"],
};
</script>
<style scoped>
.filters > span {
padding: 2px 4px;
margin-right: 4px;
border: 1px solid transparent;
}
.filters > span.seleted {
border-color: rgba(173, 47, 47, 0.2);
}
</style>