Routing System
\\nIn the previous chapter, our task management logic was made persistent via Pinia. Now we need to inject a soul into the applicationβthe Routing System.
\\nIn enterprise-level development, routing is not just about switching pages; it also bears the heavy responsibility of security (permission control) and user experience (dynamic titles).
\\n\\n
Installation and Basic Configuration
\\nFirst, install Vue Router:
\\nnpm install vue-router\\nCreate a router directory under the src directory, and create a router/index.js file.
We will define two pages: Dashboard and Login.
\\nExample
\\nimport{ createRouter, createWebHistory } from 'vue-router'\\n\\nconst routes =[\\n\\n{\\n\\n path:'/login',\\n\\n name:'Login',\\n\\n component:()=>import('@/views/Login.vue'),\\n\\n meta:{ title:'Login - TaskHub', requiresAuth:false}\\n\\n},\\n\\n{\\n\\n path:'/',\\n\\n name:'Dashboard',\\n\\n component:()=>import('@/views/Dashboard.vue'),\\n\\n// Route meta information: used to store custom data\\n\\n meta:{\\n\\n title:'My Workspace',\\n\\n requiresAuth:true,\\n\\n breadcrumb:'Home'\\n\\n}\\n\\n}\\n\\n]\\n\\nconst router = createRouter({\\n\\n history: createWebHistory(),\\n\\n routes\\n\\n})\\n\\nexport default router\\nCreate a views folder under src and assign the pages accordingly.
- \\n
src/App.vue: The root container, containing only<router-view />and global animations. \\nsrc/views/Dashboard.vue: The original core functionality of TaskHub (task list, input box, etc.). \\nsrc/views/Login.vue: The newly added login page. \\n
The complete structure is as follows:
\\nRefactor App.vue (into a clean container)
\\nExample
\\n<template>\\n\\n<div class="min-h-screen bg-slate-50">\\n\\n<router-view v-slot="{ Component }">\\n\\n<transition name="page" mode="out-in">\\n\\n<component :is="Component"/>\\n\\n</transition>\\n\\n</router-view>\\n\\n</div>\\n\\n</template>\\n\\n<style>\\n\\n /* Fade in/out effect for page transitions */\\n\\n .page-enter-active, .page-leave-active {\\n\\n transition: opacity 0.2s ease;\\n\\n }\\n\\n .page-enter-from, .page-leave-to {\\n\\n opacity: 0;\\n\\n }\\n\\n</style>\\nMigrate the original logic into Dashboard.vue
\\nCut all the code previously written in App.vue (importing Store, importing components, template layout) and paste it into src/views/Dashboard.vue.
Dashboard.vue File Code
\\n<script setup>\\n\\n import { storeToRefs } from 'pinia'\\n\\n import { useTaskStore } from '@/stores/taskStore'\\n\\n import { useRouter } from 'vue-router' // Import router\\n\\n import TaskHeader from '@/components/TaskHeader.vue'\\n\\n import TaskInput from '@/components/TaskInput.vue'\\n\\n import TaskFilter from '@/components/TaskFilter.vue'\\n\\n import TaskItem from '@/components/TaskItem.vue'\\n\\nconst taskStore = useTaskStore()\\n\\n const router = useRouter()\\n\\n const { filter, filteredTasks } = storeToRefs(taskStore)\\n\\n const { addTask, removeTask, toggleTask } = taskStore\\n\\n// Logout method\\n\\n const handleLogout = () => {\\n\\n localStorage.removeItem('isLoggedIn')\\n\\n router.push('/login')\\n\\n }\\n\\n</script>\\n\\n<template>\\n\\n<div class="py-12 px-4">\\n\\n<div class="max-w-md mx-auto bg-white rounded-3xl shadow-xl border border-slate-100 overflow-hidden">\\n\\n<TaskHeader />\\n\\n<main class="p-6">\\n\\n<TaskInput @add-task="addTask"/>\\n\\n<TaskFilter v-model="filter"/>\\n\\n<ul class="space-y-3">\\n\\n<TransitionGroup name="list">\\n\\n<TaskItem \\n\\nv-for="task in filteredTasks"\\n\\n:key="task.id"\\n\\n:task="task"\\n\\n@toggle="toggleTask"\\n\\n@remove="removeTask"\\n\\n/>\\n\\n</TransitionGroup>\\n\\n</ul>\\n\\n<button\\n\\n@click="handleLogout"\\n\\nclass="mt-8 w-full py-2 text-xs text-slate-400 hover:text-red-500 transition-colors"\\n\\n>\\n\\n Log out current account\\n\\n</button>\\n\\n</main>\\n\\n</div>\\n\\n</div>\\n\\n</template>\\nLogin Logic Implementation (Login.vue)
\\nUse Tailwind v4 to quickly build a minimalist login page and simulate login behavior.
\\nExample
\\n<script setup>\\n\\n import { ref } from 'vue'\\n\\n import { useRouter } from 'vue-router'\\n\\nconst router = useRouter()\\n\\n const isLoading = ref(false)\\n\\nconst handleLogin = () => {\\n\\n isLoading.value = true\\n\\n // Simulate async request\\n\\n setTimeout(() => {\\n\\n localStorage.setItem('isLoggedIn', 'true')\\n\\n router.push('/') // LoginSuccessfully redirect to Home\\n\\n isLoading.value = false\\n\\n }, 1000)\\n\\n }\\n\\n</script>\\n\\n<template>\\n\\n<div class="min-h-screen flex items-center justify-center bg-slate-50">\\n\\n<div class="p-8 bg-white rounded-3xl shadow-xl w-full max-w-sm border border-slate-100">\\n\\n<h2 class="text-2xl font-black mb-6 text-slate-800">Welcome back</h2>\\n\\n<button\\n\\n @click="handleLogin"\\n\\n :disabled="isLoading"\\n\\nclass="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"\\n\\n>\\n\\n {{ isLoading ? 'Loginin...' : 'One-click system access' }}\\n\\n</button>\\n\\n<p class="mt-4 text-center text-xs text-slate-400">Test environment: Click to Login</p>\\n\\n</div>\\n\\n</div>\\n\\n</template>\\n\\n
Perfect the interception logic in router/index.js
\\nEnsure that security check mode is enabled in your route configuration:
\\nExample
\\nimport{ createRouter, createWebHistory } from 'vue-router'\\n\\nconst routes =[\\n\\n{\\n\\n path:'/login',\\n\\n name:'Login',\\n\\n component:()=>import('@/views/Login.vue'),\\n\\n meta:{ title:'Login - TaskHub', requiresAuth:false}\\n\\n},\\n\\n{\\n\\n path:'/',\\n\\n name:'Dashboard',\\n\\n component:()=>import('@/views/Dashboard.vue'),\\n\\n// Route meta information: used to store custom data\\n\\n meta:{\\n\\n title:'My Workspace',\\n\\n requiresAuth:true,\\n\\n breadcrumb:'Home'\\n\\n}\\n\\n}\\n\\n]\\n\\nconst router = createRouter({\\n\\n history: createWebHistory(),\\n\\n routes\\n\\n})\\n\\n// src/router/index.js (Core code snippet)\\n\\n router.beforeEach((to, from, next)=>{\\n\\nconst isAuthenticated = localStorage.getItem('isLoggedIn')==='true'\\n\\n// If navigating to Home without Login -> Redirect to Login\\n\\nif(to.meta.requiresAuth&&!isAuthenticated){\\n\\n next('/login')\\n\\n}\\n\\n// If already logged in but still redirecting to the Login page -> Redirect to Home\\n\\nelse if(to.path==='/login'&& isAuthenticated){\\n\\n next('/')\\n\\n}\\n\\nelse{\\n\\n next()\\n\\n}\\n\\n})\\n\\nexport default router\\nComplete the final puzzle piece in main.js
\\nEnsure your main.js imports and mounts the router:
Example
\\nimport{ createApp } from 'vue'\\n\\nimport{ createPinia } from 'pinia'\\n\\nimport router from './router'// Import router\\n\\nimport App from './App.vue'\\n\\nimport'./style.css'\\n\\nconst app = createApp(App)\\n\\n app.use(createPinia())\\n\\n app.use(router)// 2. Register routes\\n\\n app.mount('#app')\\nFinal Result
\\n- \\n
- Access /: If you are not logged in, the page will flash briefly and then redirect to
/login. \\n - Access /login: Click the button, the local storage
isLoggedInbecomestrue, and it instantly switches to the dashboard. \\n - Refresh Page: Pinia will read tasks from LocalStorage, and the route guard will check the login status; no data will be lost. \\n
After logging in, you can click to log out:
\\nKey Concepts Explanation
\\n- \\n
- Why did
App.vuebecome empty?
\\nIn a Single Page Application (SPA),App.vueis the shell of the entire application. We emptied it so that we can dynamically insert different pages (Dashboard or Login) into it based on URL changes. \\n - Practical use of
useRouter:
\\nInDashboard.vue, we implemented the logout function viarouter.push('/login'). Note:pushadds a new record to the browser history, so clicking the browser back button can go back. \\n - Tailwind v4 Layout Inheritance:
\\nSince we addedbg-slate-50andmin-h-screento the global container inApp.vue, all sub-pages (Dashboard, Login) will inherit this light gray background by default, ensuring visual consistency. \\n
YouTip