vue3学习之待办事项列表(Todo List)

embedded/2025/2/14 1:12:15/

通过vite创建vue3项目,具体查看vite官网,安装依赖,引入element组件,操作查看elementPlus
components下创建TodoList组件

<template><div class="todo-list"><h1>Todo List</h1><p class="subtit">The completion of every small goal is a step towards success.</p><div class="list-box"><div class="add-btn-box"><buttonclass="add-btn"@click="dialogVisible=true">+添加任务 </button></div><divclass="list"v-if="list.length"><templatev-for="(item) in list":key="item.id"><TodoItem:data="item"@change="handleChange"@delete="handleDelete"/></template></div><div v-else><el-empty description="暂无任务" /></div></div><p class="statistics">已完成 {{finishedCount}} 项任务,还有 {{todoCount}} 项待完成</p><TodoFormv-model:visible="dialogVisible"@confirm="handleAdd"/></div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import TodoItem from './TodoItem.vue'
import TodoForm from './TodoForm.vue'
const dialogVisible = ref(false)
interface Item {title: stringfinished: booleanid: string
}
const list = ref<Item[]>([])
const getRandomRequestId = (n: number) => {let rdmNum = ''for (let i = 0; i < n; i++) {rdmNum += Math.floor(Math.random() * 10) // [0,10)的整数}return rdmNum
}
const finishedCount = computed(() => {return list.value.filter((item: Item) => item.finished).length
})
const todoCount = computed(() => {return list.value.length - finishedCount.value
})
const handleAdd = (obj: Item) => {list.value.push({ id: getRandomRequestId(3), ...obj })
}
const handleChange = (id: string, finished: booelan) => {list.value.forEach((item: Item) => {if (item.id === id) {item.finished = finished}})
}
const handleDelete = (id: string) => {list.value = list.value.filter((item: Item) => item.id !== id)
}
</script>
<style scoped>
.todo-list {display: flex;flex-direction: column;justify-content: center;align-items: center;
}
* {font-style: normal;text-transform: none;font-family: Roboto, Roboto;
}
h1 {font-weight: 700;font-size: 30px;color: #111827;line-height: 36px;text-align: center;
}
.subtit {font-weight: 400;font-size: 16px;color: #4b5563;text-align: center;
}
.list-box {width: 800px;height: 570px;padding: 20px;margin: 50px auto;box-sizing: border-box;background: #f9fafb rgba(0, 0, 0, 0.001);box-shadow: 0px 10px 15px 13px rgba(0, 0, 0, 0.1), 0px 4px 6px -4px rgba(0, 0, 0, 0.1);border-radius: 12px 12px 12px 12px;
}
.add-btn-box {margin: 16px 0;text-align: right;
}
.add-btn {width: 134px;height: 50px;background: #4f46e5;border-radius: 4px 4px 4px 4px;color: #fff;font-size: 16px;font-weight: 500;border: none;cursor: pointer;
}
.statistics {font-weight: 400;font-size: 16px;color: #6b7280;
}
</style>

创建TodoItem组件

<template><div class="todo-item"><el-checkboxv-model="currentData.finished"size="large"@change="handleChange"><span :class="['title',{finished: currentData.finished}]">{{currentData.title}}</span></el-checkbox><el-icon @click="handleDelete"><Delete /></el-icon></div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const { data } = defineProps({data: {type: Object,default: () => {},},
})
const emit = defineEmits(['change', 'delete'])
const currentData = ref(data)
const handleDelete = () => {emit('delete', currentData.value.id)
}
const handleChange = (value: boolean) => {emit('change', { id: currentData.value.id, finished: value })
}
</script>
<style scoped>
.todo-item {display: flex;justify-content: space-between;align-items: center;width: 736px;height: 72px;padding: 24px 16px;margin-bottom: 12px;background: #f9fafb;border-radius: 12px 12px 12px 12px;
}
.title {font-weight: 400;font-size: 16px;color: #4b5563;line-height: 24px;
}
.finished {color: #9ca3af;text-decoration: line-through;
}
</style>

创建TodoForm组件

<template><div class="todo-form"><el-dialogv-model="localVisible"title="Tips"width="500":before-close="handleClose"><el-formref="formRef":inline="true":model="form"class="demo-form-inline"><el-form-itemlabel="任务名称"prop="title":rules="[{required: true,message: 'Please input title',trigger: 'blur',}]"><el-inputv-model="form.title"placeholder="请输入代办事项"clearable/></el-form-item></el-form><template #footer><div class="dialog-footer"><el-button @click="handleClose">Cancel</el-button><el-buttontype="primary"@click="handleConfirm(formRef)">Confirm</el-button></div></template></el-dialog></div>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import type { FormInstance } from 'element-plus'
const props = defineProps({visible: {type: Boolean,default: false,},
})
const formRef = ref<FormInstance>()
const emit = defineEmits(['update:visible', 'confirm'])
const form = reactive({title: '',finished: false,
})
const localVisible = computed({get() {return props.visible},set(value) {emit('update:visible', value)},
})
const handleClose = () => {emit('update:visible', false)
}
const handleConfirm = (formEl: FromInstance | undefined) => {console.log(formEl)if (!formEl) returnformEl.validate((valid) => {console.log(valid, form, 'valid')if (valid) {emit('confirm', form)form.title = ''handleClose()}})
}
</script>
<style scoped>
</style>

在App.vue中


<script setup lang="ts">
import TodoList from './components/todo/TodoList.vue'
</script><template><div class="app"><TodoList /></div><RouterView />
</template>

main.ts

import './assets/main.css'import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'import App from './App.vue'
import router from './router'const app = createApp(App)app.use(createPinia())
app.use(router)
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.mount('#app')

在这里插入图片描述


http://www.ppmy.cn/embedded/162006.html

相关文章

【C++语言】C++入门

一、命名空间 在 C/C 中&#xff0c;变量、函数和后面的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;用于避免命名冲突或者名字污染&#xff0c;na…

HarmonyOS第一课第二章习题答案

判断 1. 如果代码中涉及到一些网络、数据库、传感器等功能的开发&#xff0c;均可使用预览器进行预览。 答案&#xff1a;错误 2. module.json5文件中的deviceTypes字段中&#xff0c;配置了phone&#xff0c;tablet&#xff0c;2in1等多种设备类型&#xff0c;才能进行多设…

SickOs 1.2靶机(超详细教学)

靶机地址&#xff1a;https://www.vulnhub.com/entry/sickos-12,144/ 一、主机发现 使用 arp-scan -l查找靶机ip地址 靶机ip地址为192.168.55.146 攻击机的ip地址为192.168.55.129 二、进行端口扫描、目录枚举、指纹识别 1.端口扫描 nmap 192.168.55.146发现靶机只有22和…

机器学习数学基础:22.对称矩阵的对角化

一、核心概念详解 &#xff08;一&#xff09;内积 定义与公式&#xff1a;在 n n n维向量空间中&#xff0c;对于向量 x ⃗ ( x 1 , x 2 , ⋯ , x n ) \vec{x}\ (x_1,x_2,\cdots,x_n) x (x1​,x2​,⋯,xn​)和 y ⃗ ( y 1 , y 2 , ⋯ , y n ) \vec{y}\ (y_1,y_2,\cdots,y_…

性格测评小程序01需求分析

目录 1 MBTI 性格测评工具2 MBTI 的四个核心维度3 测评搭建的思路3.1 【外向 vs 内向&#xff08;E/I&#xff09;】&#xff08;10 题&#xff0c;每题得分范围&#xff1a;0.5&#xff5e;3.2&#xff0c;较高数值表示偏向外向&#xff09;3.2 【感觉 vs 直觉&#xff08;S/N…

Django开发入门 – 4.创建Django app

Django开发入门 – 4.创建Django app Create A Django App Under An Existing Project By JacksonML 1. 什么是Django app? Django项目面向Web应用程序&#xff0c;它会由一个或多个子模块组成&#xff0c;这些子模块称为apps。 Django apps负责执行完整Web应用程序中涉及…

CEF132编译指南 MacOS 篇 - 构建 CEF (六)

1. 引言 经过前面一系列的精心准备&#xff0c;我们已经完成了所有必要的环境配置和源码获取工作。本篇作为 CEF132 编译指南系列的第六篇&#xff0c;将详细介绍如何在 macOS 系统上构建 CEF132。通过配置正确的编译命令和参数&#xff0c;我们将完成 CEF 的构建工作&#xf…

【DeepSeek】DeepSeek小模型蒸馏与本地部署深度解析DeepSeek小模型蒸馏与本地部署深度解析

一、引言与背景 在人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;如DeepSeek以其卓越的自然语言理解和生成能力&#xff0c;推动了众多应用场景的发展。然而&#xff0c;大型模型的高昂计算和存储成本&#xff0c;以及潜在的数据隐私风险&#xff0c;限制了…