Система маршрутизации

В предыдущей главе наша логика управления задачами достигла персистентности с помощью Pinia. Теперь нам нужно вдохнуть душу в приложение —Система маршрутизации。

В разработке корпоративного уровня маршрутизация — это не только переключение страниц; она также принимает на себяБезопасность (контроль прав доступа)иПользовательский опыт (динамические заголовки)как важную ответственность.


Установка и базовая конфигурация

Сначала установите Vue Router:

npm install vue-router

ВsrcСоздайте каталог router в директории и создайтеrouter/index.jsфайл.

Мы определим две страницы: Dashboard (рабочая область) и Login (страница входа).

Пример

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { title: 'Login - TaskHub', requiresAuth: false }
  },
  {
    path: '/',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    // Информация о метаданных маршрута meta: используется для хранения пользовательских данных
    meta: {
      title: 'Моя панель управления',
      requiresAuth: true,
      breadcrumb: 'Главная'
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

ВsrcСоздайте вviewsпапке и распределите страницы.

  • src/App.vue: корневой контейнер, помещайте только<router-view />и глобальные анимации.
  • src/views/Dashboard.vue: оригинальные основные функции TaskHub (список задач, поле ввода и т. д.).
  • src/views/Login.vue: недавно добавленная страница входа.
Полная структура выглядит следующим образом:

РефакторингApp.vue(Сделайте его чистым контейнером)

Пример

<template>
  <div class="min-h-screen bg-slate-50">
    <router-view v-slot="{ Component }">
      <transition name="page" mode="out-in">
        <component :is="Component" />
      </transition>
    </router-view>
  </div>
</template>

<style>
/* Эффект затухания для переключения страниц */
.page-enter-active, .page-leave-active {
  transition: opacity 0.2s ease;
}
.page-enter-from, .page-leave-to {
  opacity: 0;
}
</style>

Перенесите исходную логику вDashboard.vue

Переместите код, ранее находившийся вApp.vueнаписанное внутри (импорт хранилища, импорт компонентов, разметка шаблона) всё скопируйте и вставьте вsrc/views/Dashboard.vue.

код файла Dashboard.vue

<script setup>
import { storeToRefs } from 'pinia'
import { useTaskStore } from '@/stores/taskStore'
import { useRouter } from 'vue-router' // Импорт роутера
import TaskHeader from '@/components/TaskHeader.vue'
import TaskInput from '@/components/TaskInput.vue'
import TaskFilter from '@/components/TaskFilter.vue'
import TaskItem from '@/components/TaskItem.vue'

const taskStore = useTaskStore()
const router = useRouter()
const { filter, filteredTasks } = storeToRefs(taskStore)
const { addTask, removeTask, toggleTask } = taskStore

// Метод выхода
const handleLogout = () => {
  localStorage.removeItem('isLoggedIn')
  router.push('/login')
}
</script>

<template>
  <div class="py-12 px-4">
    <div class="max-w-md mx-auto bg-white rounded-3xl shadow-xl border border-slate-100 overflow-hidden">
      <TaskHeader />
      <main class="p-6">
        <TaskInput @add-task="addTask" />
        <TaskFilter v-model="filter" />
       
        <ul class="space-y-3">
          <TransitionGroup name="list">
            <TaskItem
             v-for="task in filteredTasks"
             :key="task.id"
             :task="task"
             @toggle="toggleTask"
             @remove="removeTask"
           />
          </TransitionGroup>
        </ul>

        <button
         @click="handleLogout"
         class="mt-8 w-full py-2 text-xs text-slate-400 hover:text-red-500 transition-colors"
       >
Выйти из текущего аккаунта
        </button>
      </main>
    </div>
  </div>
</template>

Реализация логики входа (Login.vue)

Используйте Tailwind v4, чтобы быстро создать минимальную страницу входа и сымитировать поведение входа.

Пример

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const isLoading = ref(false)

const handleLogin = () => {
  isLoading.value = true
// Имитация асинхронного запроса
  setTimeout(() => {
    localStorage.setItem('isLoggedIn', 'true')
router.push('/') // Перейти на главную страницу после успешного входа
    isLoading.value = false
  }, 1000)
}
</script>

<template>
  <div class="min-h-screen flex items-center justify-center bg-slate-50">
    <div class="p-8 bg-white rounded-3xl shadow-xl w-full max-w-sm border border-slate-100">
      <h2 class="text-2xl font-black mb-6 text-slate-800">С возвращением</h2>
      <button
      @click="handleLogin"
      :disabled="isLoading"
      class="w-full bg-linear-to-r from-blue-600 to-indigo-600 text-white py-3 rounded-xl font-bold hover:opacity-90 active:scale-95 transition-all disabled:opacity-50"
    >
{{ isLoading ? 'Выполняется вход...' : 'Войти в систему в один клик' }}
      </button>
      <p class="mt-4 text-center text-xs text-slate-400">Тестовая среда: нажмите, чтобы войти</p>
    </div>
  </div>
</template>

Улучшитьrouter/index.jsлогику перехвата

Убедитесь, что режим проверки безопасности включён в конфигурации маршрутов:

Пример

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { title: 'Login - TaskHub', requiresAuth: false }
  },
  {
    path: '/',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    // Информация о метаданных маршрута meta: используется для хранения пользовательских данных
    meta: {
      title: 'Моя панель управления',
      requiresAuth: true,
      breadcrumb: 'Главная'
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})
// src/router/index.js (основной фрагмент)
router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('isLoggedIn') === 'true'

  // Если переходим на главную страницу, но не авторизованы -> перейти на страницу входа
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login')
  }
  // Если уже авторизован, но всё ещё хочет перейти на страницу входа -> вернуться на главную
  else if (to.path === '/login' && isAuthenticated) {
    Далее('/')
  }
  else {
    Далее()
  }
})
export default router

Вmain.jsЗавершите последнюю часть в

Убедитесь, что вашmain.jsимпортирует и подключает router:

Пример

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router' // Импорт router
import App from './App.vue'
import './style.css'

const app = createApp(App)
app.use(createPinia())
app.use(router) // 2. Регистрация router
app.mount('#app')

Итоговый эффект

  1. Перейдите на /:If you're not logged in, the page will flash briefly and then redirect to/login.
  2. Перейдите на /login:Click the button, local storageisLoggedInbecomestrue, затем мгновенно переключиться на дашборд.
  3. Обновить страницу: Pinia прочитает задачи из LocalStorage, а страж маршрута проверит статус входа. Данные не будут потеряны.

После входа в систему вы можете нажать, чтобы выйти из системы:

Объяснение ключевых понятий

  1. ПочемуApp.vueстал пустым?
    В одностраничном приложении (SPA),App.vueявляется оболочкой всего приложения. Мы сделали её пустой, чтобы динамически вставлять в неё разные страницы (Dashboard или Login) в зависимости от изменений URL.
  2. useRouterпрактического занятия:
    ВDashboard.vueВ этом мы используемrouter.push('/login')чтобы реализовать функцию выхода. Примечание:pushэто добавит новую запись в историю браузера, и нажатие кнопки «Назад» в браузере позволит вернуться назад.
  3. Наследование макета в Tailwind v4:
    Поскольку мыApp.vueдобавили в глобальный контейнерbg-slate-50иmin-h-screen, все дочерние страницы (Dashboard, Login) по умолчанию унаследуют этот светло-серый фон, обеспечивая визуальную согласованность.
Другие расширения