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

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

components下创建TodoList组件

js 复制代码
<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组件

js 复制代码
<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组件

js 复制代码
<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中

js 复制代码
<script setup lang="ts">
import TodoList from './components/todo/TodoList.vue'
</script>

<template>

  <div class="app">
    <TodoList />
  </div>

  <RouterView />
</template>

main.ts

js 复制代码
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')
相关推荐
Dxy12393102167 分钟前
MySQL索引完整教程:创建、查看、修改、删除与日常管理
前端·javascript·mysql
端庄的战斗机2 小时前
javascript 设计模式(文章很长,请自备瓜子,水果和眼药水)
开发语言·javascript·设计模式
茯苓gao3 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
前端毕业班4 小时前
uni-app 小程序支持 teleport 了
前端·javascript·vue.js
东风破_5 小时前
LLM 为什么记不住你?无状态、messages 和上下文管理
javascript·人工智能
Hilaku5 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
古夕6 小时前
多应用同域部署下 Favicon 异常问题排查与治理
前端·javascript
逗逗豆巴吧6 小时前
WPS 加载项中实现当前文档直接上传
前端·javascript·ecmascript 6
妙码生花6 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十一):网络请求封装优化
前端·javascript·vue.js
用户11481867894846 小时前
普通Fetch、Fetch流式、Axios、SSE:谁才是流式输出的正确操作?
前端·javascript