用 Lyt.js 构建 Todo 应用:完整教程

用 Lyt.js 构建 Todo 应用:完整教程

通过一个经典的 Todo 应用,学习 Lyt.js v6.6.0 的核心用法。

最终效果

  • 添加 Todo
  • 标记完成/未完成
  • 删除 Todo
  • 筛选(全部/已完成/未完成)
  • 统计待办数量

步骤 1:创建项目

bash 复制代码
# 使用 Lyt.js CLI 创建项目
npm create lyt@latest my-todo-app

cd my-todo-app
npm install
npm run dev

或者手动创建:

bash 复制代码
mkdir my-todo-app
cd my-todo-app
npm init -y
npm install @lytjs/core @lytjs/plugin-vite vite

# 创建目录结构
mkdir -p src/components

步骤 2:配置 Vite

typescript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import lyt from '@lytjs/plugin-vite'

export default defineConfig({
  plugins: [lyt()],
})
json 复制代码
// package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

步骤 3:创建入口文件

html 复制代码
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Todo App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>
typescript 复制代码
// src/main.ts
import { createApp } from '@lytjs/core'
import App from './App.lyt'

createApp(App).mount('#app')

步骤 4:定义 Todo 类型

typescript 复制代码
// src/types.ts
export interface Todo {
  id: number
  text: string
  completed: boolean
}

export type FilterType = 'all' | 'active' | 'completed'

步骤 5:创建 TodoItem 组件

html 复制代码
<!-- src/components/TodoItem.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'

const props = defineProps<{
  todo: Todo
}>()

const emit = defineEmits<{
  toggle: [id: number]
  delete: [id: number]
}>()
</script>

<template>
  <li class="todo-item" :class="{ completed: todo.completed }">
    <label>
      <input
        type="checkbox"
        :checked="todo.completed"
        on:change="emit('toggle', todo.id)"
      />
      <span>{{ todo.text }}</span>
    </label>
    <button class="delete-btn" on:click="emit('delete', todo.id)">×</button>
  </li>
</template>

<style>
.todo-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

.todo-item.completed span {
  text-decoration: line-through;
  color: #999;
}

.delete-btn {
  background: none;
  border: none;
  color: #ff4d4f;
  font-size: 20px;
  cursor: pointer;
}
</style>

步骤 6:创建 TodoList 组件

html 复制代码
<!-- src/components/TodoList.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'
import TodoItem from './TodoItem.lyt'

const props = defineProps<{
  todos: Todo[]
}>()

const emit = defineEmits<{
  toggle: [id: number]
  delete: [id: number]
}>()
</script>

<template>
  <ul class="todo-list">
    <TodoItem
      each="todo in todos"
      :todo="todo"
      on:toggle="emit('toggle', $event)"
      on:delete="emit('delete', $event)"
    />
  </ul>
</template>

<style>
.todo-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
</style>

步骤 7:创建 TodoInput 组件

html 复制代码
<!-- src/components/TodoInput.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'

const emit = defineEmits<{
  add: [text: string]
}>()

let inputText = ''

function handleSubmit() {
  const text = inputText.trim()
  if (text) {
    emit('add', text)
    inputText = ''
  }
}
</script>

<template>
  <form class="todo-input" on:submit.prevent="handleSubmit">
    <input
      v-model="inputText"
      type="text"
      placeholder="添加新待办..."
    />
    <button type="submit">添加</button>
  </form>
</template>

<style>
.todo-input {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

.todo-input button {
  padding: 10px 20px;
  background: #1890ff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

步骤 8:创建 TodoFilter 组件

html 复制代码
<!-- src/components/TodoFilter.lyt -->
<script setup>
import { defineProps, defineEmits } from '@lytjs/core'
import type { FilterType } from '../types'

const props = defineProps<{
  filter: FilterType
  counts: { all: number; active: number; completed: number }
}>()

const emit = defineEmits<{
  'update:filter': [filter: FilterType]
}>()

const filters: FilterType[] = ['all', 'active', 'completed']
const labels = { all: '全部', active: '待完成', completed: '已完成' }
</script>

<template>
  <div class="todo-filter">
    <span class="count">{{ counts.active }} 项待完成</span>
    <div class="filter-buttons">
      <button
        each="f in filters"
        :class="{ active: filter === f }"
        on:click="emit('update:filter', f)"
      >
        {{ labels[f] }}
      </button>
    </div>
  </div>
</template>

<style>
.todo-filter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  color: #666;
}

.filter-buttons {
  display: flex;
  gap: 8px;
}

.filter-buttons button {
  padding: 6px 12px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 4px;
  cursor: pointer;
}

.filter-buttons button.active {
  background: #1890ff;
  color: white;
  border-color: #1890ff;
}
</style>

步骤 9:创建主 App 组件

html 复制代码
<!-- src/App.lyt -->
<script setup>
import { ref, computed } from '@lytjs/core'
import type { Todo, FilterType } from './types'
import TodoInput from './components/TodoInput.lyt'
import TodoList from './components/TodoList.lyt'
import TodoFilter from './components/TodoFilter.lyt'

let todos = ref<Todo[]>([])
let nextId = 1
let filter = ref<FilterType>('all')

const filteredTodos = computed(() => {
  switch (filter.value) {
    case 'active':
      return todos.value.filter(t => !t.completed)
    case 'completed':
      return todos.value.filter(t => t.completed)
    default:
      return todos.value
  }
})

const counts = computed(() => ({
  all: todos.value.length,
  active: todos.value.filter(t => !t.completed).length,
  completed: todos.value.filter(t => t.completed).length,
}))

function addTodo(text: string) {
  todos.value.push({
    id: nextId++,
    text,
    completed: false,
  })
}

function toggleTodo(id: number) {
  const todo = todos.value.find(t => t.id === id)
  if (todo) {
    todo.completed = !todo.completed
  }
}

function deleteTodo(id: number) {
  const index = todos.value.findIndex(t => t.id === id)
  if (index > -1) {
    todos.value.splice(index, 1)
  }
}
</script>

<template>
  <div class="app">
    <h1>我的待办</h1>
    <TodoInput on:add="addTodo" />
    <TodoList
      if="filteredTodos.length > 0"
      :todos="filteredTodos"
      on:toggle="toggleTodo"
      on:delete="deleteTodo"
    />
    <p else class="empty">暂无待办事项</p>
    <TodoFilter
      if="counts.all > 0"
      :filter="filter"
      :counts="counts"
      on:update:filter="filter = $event"
    />
  </div>
</template>

<style>
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #f5f5f5;
}

.app {
  max-width: 500px;
  margin: 50px auto;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

h1 {
  text-align: center;
  color: #333;
  margin-bottom: 20px;
}

.empty {
  text-align: center;
  color: #999;
  padding: 20px;
}
</style>

步骤 10:运行项目

bash 复制代码
npm run dev

访问 http://localhost:5173,你应该能看到完整的 Todo 应用。


完整目录结构

css 复制代码
my-todo-app/
├── index.html
├── package.json
├── vite.config.ts
└── src/
    ├── main.ts
    ├── App.lyt
    ├── types.ts
    └── components/
        ├── TodoItem.lyt
        ├── TodoList.lyt
        ├── TodoInput.lyt
        └── TodoFilter.lyt

扩展练习

  1. 添加编辑功能:双击编辑 Todo 文字
  2. 持久化存储:使用 localStorage 保存数据
  3. 添加动画 :使用 <Transition> 添加过渡效果
  4. 统计面板:显示完成百分比

关键语法回顾

功能 Lyt.js 语法
条件渲染 if="condition"
列表渲染 each="item in list"
双向绑定 v-model="value" (模板中) / model="value" (Lyt.js)
事件绑定 on:click="handler"
Props defineProps<T>()
Emits defineEmits<T>()
响应式数据 ref() / reactive()
计算属性 computed()
相关推荐
格子软件13 分钟前
2026年GEO优化系统源码级状态机与多模型调度拆解
java·前端·vue.js·人工智能·vue·geo
HUMHSX1 小时前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货1 小时前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙0071 小时前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由1 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317422 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登2 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户2136610035722 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月2 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript
李明卫杭州2 小时前
Vue2 中 v-model 处理不同数据结构的技巧
前端·javascript·vue.js