Чтобы закрепить все пройденные темы курса и на практике увидеть, как Vue.js 3, Composables и Pinia могут работать вместе, давайте создадим небольшое, но небанальное приложение для управления задачами. Это не просто классическое ToDo, где вы добавляете и удаляете задачи — мы сделаем несколько интересных фич, таких как фильтрация по статусам, возможность приоритизации задач и работа с локальным хранилищем.

Что мы реализуем

  • Добавление задач: Пользователь сможет добавить новую задачу, указав её текст и приоритет.
  • Фильтрация задач: Фильтрация по статусам (все, активные, выполненные).
  • Изменение статуса задачи: Возможность отметить задачу как выполненную.
  • Приоритизация: Важные задачи будут выделяться в списке.
  • Сохранение задач в локальное хранилище: Чтобы задачи не терялись после перезагрузки страницы.

Шаг 1: Подготовка проекта

Используя Vite, создаём новый проект:

npm init vite@latest vue-todo --template vue-ts
cd vue-todo
npm install

Далее устанавливаем Pinia для управления состоянием:

npm install pinia

Шаг 2: Настройка Pinia

Создадим новый store для управления задачами. В файле stores/todoStore.ts:

import { defineStore } from 'pinia';
import { ref } from 'vue';

interface Task {
  id: number;
  text: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
}

export const useTodoStore = defineStore('todo', () => {
  const tasks = ref<Task[]>([]);
  const filter = ref<'all' | 'active' | 'completed'>('all');

  function addTask(text: string, priority: 'low' | 'medium' | 'high') {
    tasks.value.push({
      id: Date.now(),
      text,
      completed: false,
      priority,
    });
  }

  function toggleTaskStatus(id: number) {
    const task = tasks.value.find((task) => task.id === id);
    if (task) {
      task.completed = !task.completed;
    }
  }

  function filteredTasks() {
    if (filter.value === 'active') {
      return tasks.value.filter(task => !task.completed);
    } else if (filter.value === 'completed') {
      return tasks.value.filter(task => task.completed);
    }
    return tasks.value;
  }

  function setFilter(newFilter: 'all' | 'active' | 'completed') {
    filter.value = newFilter;
  }

  return { tasks, filter, addTask, toggleTaskStatus, filteredTasks, setFilter };
});

Здесь мы создаём store для работы с задачами, где можно добавлять задачи, менять их статус и фильтровать по состоянию. Заметьте, что мы используем реактивные данные для задач и фильтра, что позволяет нам легко управлять их изменениями.

Шаг 3: Создание компонента для отображения задач

Теперь давайте создадим компонент для отображения списка задач и управления фильтрацией. В файле components/TodoList.vue:

<template>
  <div>
    <h1>Список задач</h1>
    
    <div>
      <label>
        Фильтр:
        <select v-model="filter">
          <option value="all">Все</option>
          <option value="active">Активные</option>
          <option value="completed">Завершенные</option>
        </select>
      </label>
    </div>
    
    <ul>
      <li v-for="task in filteredTasks" :key="task.id">
        <input type="checkbox" v-model="task.completed" @change="toggleTaskStatus(task.id)" />
        <span :style="{ fontWeight: task.priority === 'high' ? 'bold' : 'normal' }">{{ task.text }}</span>
        <span> ({{ task.priority }})</span>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { useTodoStore } from '../stores/todoStore';
import { computed } from 'vue';

const todoStore = useTodoStore();
const filter = computed(() => todoStore.filter);
const filteredTasks = computed(() => todoStore.filteredTasks());
const toggleTaskStatus = todoStore.toggleTaskStatus;
</script>

Здесь мы реализовали список задач с возможностью фильтрации по статусу и изменению статуса задачи. Каждая задача отображается с её приоритетом — важные задачи выделены жирным шрифтом.

Шаг 4: Форма добавления задач

Добавим форму для добавления новых задач. В файле components/AddTask.vue:

<template>
  <form @submit.prevent="submitForm">
    <input v-model="taskText" placeholder="Введите задачу" />
    <select v-model="taskPriority">
      <option value="low">Низкий</option>
      <option value="medium">Средний</option>
      <option value="high">Высокий</option>
    </select>
    <button type="submit">Добавить</button>
  </form>
</template>

<script setup>
import { ref } from 'vue';
import { useTodoStore } from '../stores/todoStore';

const taskText = ref('');
const taskPriority = ref('low');
const todoStore = useTodoStore();

function submitForm() {
  if (taskText.value.trim()) {
    todoStore.addTask(taskText.value, taskPriority.value);
    taskText.value = '';
    taskPriority.value = 'low';
  }
}
</script>

Эта форма позволяет пользователю ввести текст задачи, выбрать её приоритет и добавить задачу в общий список. При отправке формы данные передаются в Pinia store.

Шаг 5: Подключение компонентов

Теперь соберём всё вместе в основном приложении. В файле App.vue:

<template>
  <div>
    <AddTask />
    <TodoList />
  </div>
</template>

<script setup>
import AddTask from './components/AddTask.vue';
import TodoList from './components/TodoList.vue';
</script>

Шаг 6: Сохранение задач в локальное хранилище

Чтобы задачи сохранялись между перезагрузками страницы, добавим работу с localStorage. Изменим наш store, добавив логику сохранения и загрузки данных:

import { defineStore } from 'pinia';
import { ref, watch } from 'vue';

interface Task {
  id: number;
  text: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
}

export const useTodoStore = defineStore('todo', () => {
  const tasks = ref<Task[]>(JSON.parse(localStorage.getItem('tasks') || '[]'));
  const filter = ref<'all' | 'active' | 'completed'>('all');

  function addTask(text: string, priority: 'low' | 'medium' | 'high') {
    tasks.value.push({
      id: Date.now(),
      text,
      completed: false,
      priority,
    });
  }

  function toggleTaskStatus(id: number) {
    const task = tasks.value.find((task) => task.id === id);
    if (task) {
      task.completed = !task.completed;
    }
  }

  function filteredTasks() {
    if (filter.value === 'active') {
      return tasks.value.filter(task => !task.completed);
    } else if (filter.value === 'completed') {
      return tasks.value.filter(task => task.completed);
    }
    return tasks.value;
  }

  function setFilter(newFilter: 'all' | 'active' | 'completed') {
    filter.value = newFilter;
  }

  watch(tasks, (newTasks) => {
    localStorage.setItem('tasks', JSON.stringify(newTasks));
  }, { deep: true });

  return { tasks, filter, addTask, toggleTaskStatus, filteredTasks, setFilter };
});

Теперь задачи будут сохраняться в локальном хранилище, и при перезагрузке страницы они не исчезнут.

Заключение

Вот и всё! Мы создали небольшое, но полезное приложение для управления задачами с использованием Vue.js 3, Pinia и composables. В ходе работы мы затронули важные концепты Vue 3, такие как реактивность, компоненты, управление состоянием через Pinia, а также применили локальное хранилище для хранения данных. Это приложение можно легко расширять, добавляя новые функции, например, возможность редактировать задачи или устанавливать сроки выполнения.

 

Один комментарий