Skip to content

Todo 应用示例

一个完整的 Todo 列表应用,演示组件、Store 和模板语法的综合使用。

功能特性

  • 添加、删除、切换完成状态
  • 统计未完成数量
  • 筛选(全部/未完成/已完成)
  • 本地持久化

完整代码

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Lyt.js Todo</title>
  <script src="https://unpkg.com/lyt/dist/lyt.global.js"></script>
  <style>
    body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 0 16px; }
    h1 { color: #4f46e5; }
    .input-row { display: flex; gap: 8px; margin-bottom: 16px; }
    .input-row input { flex: 1; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 16px; }
    .input-row button { padding: 8px 16px; background: #4f46e5; color: white; border: none; border-radius: 6px; cursor: pointer; }
    .filters { display: flex; gap: 8px; margin-bottom: 16px; }
    .filters button { padding: 4px 12px; border: 1px solid #d1d5db; border-radius: 4px; background: white; cursor: pointer; }
    .filters button.active { background: #4f46e5; color: white; border-color: #4f46e5; }
    .todo-item { display: flex; align-items: center; gap: 8px; padding: 8px; border-bottom: 1px solid #f3f4f6; }
    .todo-item.done span { text-decoration: line-through; color: #9ca3af; }
    .todo-item span { flex: 1; }
    .todo-item button { background: none; border: none; color: #ef4444; cursor: pointer; font-size: 18px; }
    .stats { margin-top: 16px; color: #6b7280; font-size: 14px; }
  </style>
</head>
<body>
  <div id="app"></div>
  <script>
    const { createApp, ref, computed, watch, createStore } = Lyt

    // 创建 Todo Store
    const useTodoStore = createStore('todos', {
      state: () => ({
        todos: JSON.parse(localStorage.getItem('lyt-todos') || '[]'),
        filter: 'all',
        newTodo: ''
      }),

      getters: {
        filteredTodos: (state) => {
          if (state.filter === 'active') return state.todos.filter(t => !t.done)
          if (state.filter === 'done') return state.todos.filter(t => t.done)
          return state.todos
        },
        remaining: (state) => state.todos.filter(t => !t.done).length,
        total: (state) => state.todos.length
      },

      actions: {
        addTodo() {
          const text = this.state.newTodo.trim()
          if (!text) return
          this.state.todos.push({ id: Date.now(), text, done: false })
          this.state.newTodo = ''
        },
        removeTodo(id) {
          this.state.todos = this.state.todos.filter(t => t.id !== id)
        },
        toggleTodo(id) {
          const todo = this.state.todos.find(t => t.id === id)
          if (todo) todo.done = !todo.done
        },
        setFilter(filter) {
          this.state.filter = filter
        }
      }
    })

    const app = createApp({
      init() {
        this.store = useTodoStore

        // 持久化
        watch(
          () => this.store.state.todos,
          (todos) => {
            localStorage.setItem('lyt-todos', JSON.stringify(todos))
          }
        )
      },

      template: `
        <div>
          <h1>Todo 应用</h1>
          <div class="input-row">
            <input
              v-bind:model="store.state.newTodo"
              @keyup.enter="store.actions.addTodo()"
              placeholder="添加新任务..."
            />
            <button @click="store.actions.addTodo()">添加</button>
          </div>
          <div class="filters">
            <button @click="store.actions.setFilter('all')" :class="{ active: store.state.filter === 'all' }">全部</button>
            <button @click="store.actions.setFilter('active')" :class="{ active: store.state.filter === 'active' }">未完成</button>
            <button @click="store.actions.setFilter('done')" :class="{ active: store.state.filter === 'done' }">已完成</button>
          </div>
          <div>
            <div v-each="todo in store.getters.filteredTodos" class="todo-item" :class="{ done: todo.done }">
              <input type="checkbox" :checked="todo.done" @change="store.actions.toggleTodo(todo.id)" />
              <span>{{ todo.text }}</span>
              <button @click="store.actions.removeTodo(todo.id)">&times;</button>
            </div>
          </div>
          <div class="stats">
            {{ store.getters.remaining }} / {{ store.getters.total }} 项未完成
          </div>
        </div>
      `
    })

    app.mount('#app')
  </script>
</body>
</html>

代码解析

1. Store 状态管理

使用 createStore 集中管理 Todo 数据:

ts
const useTodoStore = createStore('todos', {
  state: () => ({ todos: [], filter: 'all', newTodo: '' }),
  getters: { filteredTodos, remaining, total },
  actions: { addTodo, removeTodo, toggleTodo, setFilter }
})
  • state — 存储任务列表、筛选条件和输入值
  • getters — 派生数据(筛选后的列表、统计信息)
  • actions — 修改状态的操作方法

2. 模板语法综合使用

语法用途
v-bind:model双向绑定输入框
@click点击事件处理
@keyup.enter回车事件处理
v-each列表渲染
:class动态 class 绑定
:checked复选框状态绑定
文本插值

3. 数据持久化

使用 watch 侦听 todos 变化,自动保存到 localStorage:

ts
watch(
  () => this.store.state.todos,
  (todos) => localStorage.setItem('lyt-todos', JSON.stringify(todos))
)

TIP

这个示例综合展示了 Store、响应式系统和模板语法的配合使用。更多 Store 用法请参阅 状态管理

基于 MIT 许可发布