一个vue3的待办列表组件, 仿企业微信的待办列表
TodoList.vue
<template>
<div>
<el-input v-model="todoInput" placeholder="写下你的待办事项..." class="el-input" @keyup.enter="addTodo"
input-style="background-color: #EBECED;" />
<el-table :data="todos" size="small" :show-header="false" @row-dblclick="editTodo">
<el-table-column width="30">
<template #default="scope">
<el-checkbox v-model="scope.row.completed" @change="toggleTodo(scope.row)">check</el-checkbox>
</template>
</el-table-column>
<el-table-column prop="text" />
</el-table>
<div @click="showCompleted = !showCompleted" style="cursor: pointer; text-align: center; margin: 10px 0;">
<el-divider v-if="!showCompleted">已完成 (点击展开)</el-divider>
<el-divider v-else>已完成 (点击折叠)</el-divider>
</div>
<el-table :data="completedTodos" size="small" :show-header="false" v-if="showCompleted">
<el-table-column width="30">
<template #default="scope">
<el-checkbox v-model="scope.row.completed" @change="toggleTodo(scope.row)">check</el-checkbox>
</template>
</el-table-column>
<el-table-column prop="text" />
</el-table>
<el-dialog title="修改" v-model="editDialogVisible">
<el-form :model="editingTodo">
<el-form-item label="内容">
<el-input ref="inputRef" v-model="editingTodo.text"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-checkbox v-model="editingTodo.completed">已完成</el-checkbox>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="editDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmEdit()">确 定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
// 通用的待办列表
// 使用<TodoList :todoList="todoList" :save="save"></TodoList>
import { ref } from 'vue';
interface Todo {
id: number;
text: string;
completed: boolean;
}
defineOptions({
name: 'TodoList'
})
const props = defineProps({
todoList: Array<Todo>,
save: Function //外部传入的保存方法,入参是Todo[]
});
const todoInput = ref('');
const todos = ref<Todo[]>([]);
const completedTodos = ref<Todo[]>([]);
const editDialogVisible = ref(false);
const editingTodo = ref({} as Todo);
const showCompleted = ref(false);
const inputRef = ref();
watch(() => props.todoList, (newValue) => {
if (newValue) {
newValue.sort((a, b) => b.id - a.id);
todos.value = newValue.filter(t => !t.completed);
completedTodos.value = newValue.filter(t => t.completed);
}
})
const addTodo = () => {
if (todoInput.value.trim() === '') return;
todos.value.unshift({
id: Date.now(),
text: todoInput.value,
completed: false
});
todoInput.value = '';
saveTodoList();
};
const toggleTodo = async (todo: Todo) => {
await new Promise(resolve => setTimeout(() => resolve("delay"), 300)); //产生一个点击后动画效果
if (todo.completed) {
if (!completedTodos.value.find(t => t.id == todo.id)) {
completedTodos.value.unshift(todo);
}
todos.value = todos.value.filter(t => t.id !== todo.id);
} else {
if (!todos.value.find(t => t.id == todo.id)) {
todo.id = Date.now();//更新时间到最新,所以每个todo其实是没有唯一值的
todos.value.unshift(todo);
}
completedTodos.value = completedTodos.value.filter(t => t.id !== todo.id);
}
saveTodoList();
};
const editTodo = (todo: Todo) => {
editingTodo.value = { ...todo };
editDialogVisible.value = true;
//组件focus的正确方式 setTimeout
setTimeout(() => {
inputRef.value?.focus();
})
};
const confirmEdit = () => {
editDialogVisible.value = false;
var todo = todos.value.find(t => t.id == editingTodo.value.id)
if (todo) {
todo.text = editingTodo.value.text
todo.completed = editingTodo.value.completed;
toggleTodo(todo);
}
};
const saveTodoList = () => {
if (props.save) { // 添加空值检查
props.save(todos.value.concat(completedTodos.value));
}
}
</script>
<style scoped>
.el-input {
--el-input-bg-color: #EBECED;
}
</style>