当前位置: 首页 > article >正文

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

通过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">
        <button
          class="add-btn"
          @click="dialogVisible=true"
        >+添加任务 </button>
      </div>

      <div
        class="list"
        v-if="list.length"
      >
        <template
          v-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>
    <TodoForm
      v-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: string
  finished: boolean
  id: 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-checkbox
      v-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-dialog
      v-model="localVisible"
      title="Tips"
      width="500"
      :before-close="handleClose"
    >
      <el-form
        ref="formRef"
        :inline="true"
        :model="form"
        class="demo-form-inline"
      >
        <el-form-item
          label="任务名称"
          prop="title"
          :rules="[
              {
                required: true,
                message: 'Please input title',
                trigger: 'blur',
              }
          ]"
        >
          <el-input
            v-model="form.title"
            placeholder="请输入代办事项"
            clearable
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="handleClose">Cancel</el-button>
          <el-button
            type="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) return
  formEl.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.kler.cn/a/541652.html

相关文章:

  • 力扣算法题:反转字符串中的元音字母
  • 什么是Java虚拟机(JVM)?它的作用是什么?
  • 【进程与线程】进程之间的通信
  • ASP.NET Core 如何使用 C# 向端点发出 POST 请求
  • C语言-结构体
  • Git、Github和Gitee完整讲解:丛基础到进阶功能
  • 支持向量机原理
  • 如何在Node.js中使用中间件处理请求
  • 后盾人JS -- 模块化开发
  • 小游戏源码开发之可跨app软件对接是如何设计和开发的
  • Flutter_学习记录_基本组件的使用记录_2
  • 后盾人JS -- 异步编程,宏任务与微任务
  • HTML之JavaScript对象声明
  • MySQL下载过程
  • Flink内存配置和优化
  • 五十天精通硬件设计第27天-时域频域知识
  • Django中select_related 的作用
  • C++适用于所有输入法的解决方案(切换输入法)
  • GPIO函数详解(二)
  • pytest测试专题 - 1.2 如何获得美观的测试报告
  • 【Vue中BUG解决】npm error path git
  • ThreadLocal 原理?需要注意什么?
  • 自动控制视频讲解
  • CCFCSP备考第二天
  • 2.协同过滤算法
  • 第四期书生大模型实战营-第4关-L2G4000