2025-12-18 13:50:39 +08:00
|
|
|
# Layout Components Integration Guide
|
|
|
|
|
|
|
|
|
|
## Quick Start
|
|
|
|
|
|
|
|
|
|
### 1. Import Layout Components
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// In your view files
|
2025-12-25 08:40:12 -08:00
|
|
|
import { AppLayout, AuthLayout } from '@/components/layout'
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2. Use in Routes
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// src/router/index.ts
|
2025-12-25 08:40:12 -08:00
|
|
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
|
|
|
import type { RouteRecordRaw } from 'vue-router'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Views
|
2025-12-25 08:40:12 -08:00
|
|
|
import DashboardView from '@/views/DashboardView.vue'
|
|
|
|
|
import LoginView from '@/views/auth/LoginView.vue'
|
|
|
|
|
import RegisterView from '@/views/auth/RegisterView.vue'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
const routes: RouteRecordRaw[] = [
|
|
|
|
|
// Auth routes (no layout needed - views use AuthLayout internally)
|
|
|
|
|
{
|
|
|
|
|
path: '/login',
|
|
|
|
|
name: 'Login',
|
|
|
|
|
component: LoginView,
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: false }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/register',
|
|
|
|
|
name: 'Register',
|
|
|
|
|
component: RegisterView,
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: false }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// User routes (use AppLayout)
|
|
|
|
|
{
|
|
|
|
|
path: '/dashboard',
|
|
|
|
|
name: 'Dashboard',
|
|
|
|
|
component: DashboardView,
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, title: 'Dashboard' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/api-keys',
|
|
|
|
|
name: 'ApiKeys',
|
|
|
|
|
component: () => import('@/views/ApiKeysView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, title: 'API Keys' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/usage',
|
|
|
|
|
name: 'Usage',
|
|
|
|
|
component: () => import('@/views/UsageView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, title: 'Usage Statistics' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/redeem',
|
|
|
|
|
name: 'Redeem',
|
|
|
|
|
component: () => import('@/views/RedeemView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, title: 'Redeem Code' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/profile',
|
|
|
|
|
name: 'Profile',
|
|
|
|
|
component: () => import('@/views/ProfileView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, title: 'Profile Settings' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Admin routes (use AppLayout, admin only)
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/dashboard',
|
|
|
|
|
name: 'AdminDashboard',
|
|
|
|
|
component: () => import('@/views/admin/DashboardView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'Admin Dashboard' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/users',
|
|
|
|
|
name: 'AdminUsers',
|
|
|
|
|
component: () => import('@/views/admin/UsersView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'User Management' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/groups',
|
|
|
|
|
name: 'AdminGroups',
|
|
|
|
|
component: () => import('@/views/admin/GroupsView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'Groups' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/accounts',
|
|
|
|
|
name: 'AdminAccounts',
|
|
|
|
|
component: () => import('@/views/admin/AccountsView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'Accounts' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/proxies',
|
|
|
|
|
name: 'AdminProxies',
|
|
|
|
|
component: () => import('@/views/admin/ProxiesView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'Proxies' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/admin/redeem-codes',
|
|
|
|
|
name: 'AdminRedeemCodes',
|
|
|
|
|
component: () => import('@/views/admin/RedeemCodesView.vue'),
|
2025-12-25 08:40:12 -08:00
|
|
|
meta: { requiresAuth: true, requiresAdmin: true, title: 'Redeem Codes' }
|
2025-12-18 13:50:39 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Default redirect
|
|
|
|
|
{
|
|
|
|
|
path: '/',
|
2025-12-25 08:40:12 -08:00
|
|
|
redirect: '/dashboard'
|
|
|
|
|
}
|
|
|
|
|
]
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
const router = createRouter({
|
|
|
|
|
history: createWebHistory(),
|
2025-12-25 08:40:12 -08:00
|
|
|
routes
|
|
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Navigation guards
|
|
|
|
|
router.beforeEach((to, from, next) => {
|
2025-12-25 08:40:12 -08:00
|
|
|
const authStore = useAuthStore()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
|
|
|
|
// Redirect to login if not authenticated
|
2025-12-25 08:40:12 -08:00
|
|
|
next('/login')
|
2025-12-18 13:50:39 +08:00
|
|
|
} else if (to.meta.requiresAdmin && !authStore.isAdmin) {
|
|
|
|
|
// Redirect to dashboard if not admin
|
2025-12-25 08:40:12 -08:00
|
|
|
next('/dashboard')
|
2025-12-18 13:50:39 +08:00
|
|
|
} else {
|
2025-12-25 08:40:12 -08:00
|
|
|
next()
|
2025-12-18 13:50:39 +08:00
|
|
|
}
|
2025-12-25 08:40:12 -08:00
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
export default router
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 3. Initialize Stores in main.ts
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// src/main.ts
|
2025-12-25 08:40:12 -08:00
|
|
|
import { createApp } from 'vue'
|
|
|
|
|
import { createPinia } from 'pinia'
|
|
|
|
|
import App from './App.vue'
|
|
|
|
|
import router from './router'
|
|
|
|
|
import './style.css'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
const app = createApp(App)
|
|
|
|
|
const pinia = createPinia()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
app.use(pinia)
|
|
|
|
|
app.use(router)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Initialize auth state on app startup
|
2025-12-25 08:40:12 -08:00
|
|
|
import { useAuthStore } from '@/stores'
|
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
|
authStore.checkAuth()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
app.mount('#app')
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. Update App.vue
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- src/App.vue -->
|
|
|
|
|
<template>
|
|
|
|
|
<router-view />
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
// App.vue just renders the router view
|
|
|
|
|
// Layouts are handled by individual views
|
|
|
|
|
</script>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## View Component Templates
|
|
|
|
|
|
|
|
|
|
### Authenticated Page Template
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- src/views/DashboardView.vue -->
|
|
|
|
|
<template>
|
|
|
|
|
<AppLayout>
|
|
|
|
|
<div class="space-y-6">
|
|
|
|
|
<h1 class="text-3xl font-bold text-gray-900">Dashboard</h1>
|
|
|
|
|
|
|
|
|
|
<!-- Your content here -->
|
|
|
|
|
</div>
|
|
|
|
|
</AppLayout>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-25 08:40:12 -08:00
|
|
|
import { AppLayout } from '@/components/layout'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Your component logic here
|
|
|
|
|
</script>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Auth Page Template
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- src/views/auth/LoginView.vue -->
|
|
|
|
|
<template>
|
|
|
|
|
<AuthLayout>
|
2025-12-25 08:40:12 -08:00
|
|
|
<h2 class="mb-6 text-2xl font-bold text-gray-900">Login</h2>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
<!-- Your login form here -->
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
<p class="text-gray-600">
|
|
|
|
|
Don't have an account?
|
2025-12-25 08:40:12 -08:00
|
|
|
<router-link to="/register" class="text-indigo-600 hover:underline"> Sign up </router-link>
|
2025-12-18 13:50:39 +08:00
|
|
|
</p>
|
|
|
|
|
</template>
|
|
|
|
|
</AuthLayout>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-25 08:40:12 -08:00
|
|
|
import { AuthLayout } from '@/components/layout'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Your login logic here
|
|
|
|
|
</script>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Customization
|
|
|
|
|
|
|
|
|
|
### Changing Colors
|
|
|
|
|
|
|
|
|
|
The components use Tailwind's indigo color scheme by default. To change:
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- Change all instances of indigo-* to your preferred color -->
|
|
|
|
|
<div class="bg-blue-600"> <!-- Instead of bg-indigo-600 -->
|
|
|
|
|
<div class="text-blue-600"> <!-- Instead of text-indigo-600 -->
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Adding Custom Icons
|
|
|
|
|
|
|
|
|
|
Replace HTML entity icons with your preferred icon library:
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- Before (HTML entities) -->
|
|
|
|
|
<span class="text-lg">📈</span>
|
|
|
|
|
|
|
|
|
|
<!-- After (Heroicons example) -->
|
2025-12-25 08:40:12 -08:00
|
|
|
<ChartBarIcon class="h-5 w-5" />
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Sidebar Customization
|
|
|
|
|
|
|
|
|
|
Modify navigation items in `AppSidebar.vue`:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// Add/remove/modify navigation items
|
|
|
|
|
const userNavItems = [
|
|
|
|
|
{ path: '/dashboard', label: 'Dashboard', icon: '📈' },
|
2025-12-25 08:40:12 -08:00
|
|
|
{ path: '/new-page', label: 'New Page', icon: '📄' } // Add new item
|
2025-12-18 13:50:39 +08:00
|
|
|
// ...
|
2025-12-25 08:40:12 -08:00
|
|
|
]
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Header Customization
|
|
|
|
|
|
|
|
|
|
Modify user dropdown in `AppHeader.vue`:
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- Add new dropdown items -->
|
|
|
|
|
<router-link
|
|
|
|
|
to="/settings"
|
|
|
|
|
@click="closeDropdown"
|
|
|
|
|
class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
|
|
|
>
|
|
|
|
|
<span class="mr-2">⚙</span>
|
|
|
|
|
Settings
|
|
|
|
|
</router-link>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Mobile Responsive Behavior
|
|
|
|
|
|
|
|
|
|
### Sidebar
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- **Desktop (md+)**: Always visible, can be collapsed to icon-only view
|
|
|
|
|
- **Mobile**: Hidden by default, shown via menu toggle in header
|
|
|
|
|
|
|
|
|
|
### Header
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- **Desktop**: Shows full user info and balance
|
|
|
|
|
- **Mobile**: Shows compact view with hamburger menu
|
|
|
|
|
|
|
|
|
|
To improve mobile experience, you can add overlay and transitions:
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- AppSidebar.vue enhancement for mobile -->
|
|
|
|
|
<aside
|
2025-12-25 08:40:12 -08:00
|
|
|
class="fixed left-0 top-0 z-40 h-screen transition-transform duration-300"
|
2025-12-18 13:50:39 +08:00
|
|
|
:class="[
|
|
|
|
|
sidebarCollapsed ? 'w-16' : 'w-64',
|
|
|
|
|
// Hide on mobile when collapsed
|
|
|
|
|
'md:translate-x-0',
|
|
|
|
|
sidebarCollapsed ? '-translate-x-full md:translate-x-0' : 'translate-x-0'
|
|
|
|
|
]"
|
|
|
|
|
>
|
|
|
|
|
<!-- ... -->
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<!-- Add overlay for mobile -->
|
|
|
|
|
<div
|
|
|
|
|
v-if="!sidebarCollapsed"
|
|
|
|
|
@click="toggleSidebar"
|
2025-12-25 08:40:12 -08:00
|
|
|
class="fixed inset-0 z-30 bg-black bg-opacity-50 md:hidden"
|
2025-12-18 13:50:39 +08:00
|
|
|
></div>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## State Management Integration
|
|
|
|
|
|
|
|
|
|
### Auth Store Usage
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-25 08:40:12 -08:00
|
|
|
import { useAuthStore } from '@/stores'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
const authStore = useAuthStore()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Check if user is authenticated
|
|
|
|
|
if (authStore.isAuthenticated) {
|
|
|
|
|
// User is logged in
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user is admin
|
|
|
|
|
if (authStore.isAdmin) {
|
|
|
|
|
// User has admin role
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current user
|
2025-12-25 08:40:12 -08:00
|
|
|
const user = authStore.user
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### App Store Usage
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-25 08:40:12 -08:00
|
|
|
import { useAppStore } from '@/stores'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
2025-12-25 08:40:12 -08:00
|
|
|
const appStore = useAppStore()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Toggle sidebar
|
2025-12-25 08:40:12 -08:00
|
|
|
appStore.toggleSidebar()
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Show notifications
|
2025-12-25 08:40:12 -08:00
|
|
|
appStore.showSuccess('Operation completed!')
|
|
|
|
|
appStore.showError('Something went wrong')
|
|
|
|
|
appStore.showInfo('Did you know...')
|
|
|
|
|
appStore.showWarning('Be careful!')
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Loading state
|
2025-12-25 08:40:12 -08:00
|
|
|
appStore.setLoading(true)
|
2025-12-18 13:50:39 +08:00
|
|
|
// ... perform operation
|
2025-12-25 08:40:12 -08:00
|
|
|
appStore.setLoading(false)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
// Or use helper
|
|
|
|
|
await appStore.withLoading(async () => {
|
|
|
|
|
// Your async operation
|
2025-12-25 08:40:12 -08:00
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Accessibility Features
|
|
|
|
|
|
|
|
|
|
All layout components include:
|
|
|
|
|
|
|
|
|
|
- **Semantic HTML**: Proper use of `<nav>`, `<header>`, `<main>`, `<aside>`
|
|
|
|
|
- **ARIA labels**: Buttons have descriptive labels
|
|
|
|
|
- **Keyboard navigation**: All interactive elements are keyboard accessible
|
|
|
|
|
- **Focus management**: Proper focus states with Tailwind's `focus:` utilities
|
|
|
|
|
- **Color contrast**: WCAG AA compliant color combinations
|
|
|
|
|
|
|
|
|
|
To enhance further:
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
<!-- Add skip to main content link -->
|
|
|
|
|
<a
|
|
|
|
|
href="#main-content"
|
2025-12-25 08:40:12 -08:00
|
|
|
class="sr-only rounded bg-white px-4 py-2 focus:not-sr-only focus:absolute focus:left-4 focus:top-4"
|
2025-12-18 13:50:39 +08:00
|
|
|
>
|
|
|
|
|
Skip to main content
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
<main id="main-content">
|
|
|
|
|
<!-- Content -->
|
|
|
|
|
</main>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Testing
|
|
|
|
|
|
|
|
|
|
### Unit Testing Layout Components
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// AppHeader.test.ts
|
2025-12-25 08:40:12 -08:00
|
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
|
|
|
import { mount } from '@vue/test-utils'
|
|
|
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
|
|
|
import AppHeader from '@/components/layout/AppHeader.vue'
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
describe('AppHeader', () => {
|
|
|
|
|
beforeEach(() => {
|
2025-12-25 08:40:12 -08:00
|
|
|
setActivePinia(createPinia())
|
|
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
it('renders user info when authenticated', () => {
|
2025-12-25 08:40:12 -08:00
|
|
|
const wrapper = mount(AppHeader)
|
2025-12-18 13:50:39 +08:00
|
|
|
// Add assertions
|
2025-12-25 08:40:12 -08:00
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
it('shows dropdown when clicked', async () => {
|
2025-12-25 08:40:12 -08:00
|
|
|
const wrapper = mount(AppHeader)
|
|
|
|
|
await wrapper.find('button').trigger('click')
|
|
|
|
|
expect(wrapper.find('.dropdown').exists()).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Performance Optimization
|
|
|
|
|
|
|
|
|
|
### Lazy Loading
|
|
|
|
|
|
|
|
|
|
Views using layouts are already lazy loaded in the router example above.
|
|
|
|
|
|
|
|
|
|
### Code Splitting
|
|
|
|
|
|
|
|
|
|
Layout components are automatically code-split when imported:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// This creates a separate chunk for layout components
|
2025-12-25 08:40:12 -08:00
|
|
|
import { AppLayout } from '@/components/layout'
|
2025-12-18 13:50:39 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Reducing Re-renders
|
|
|
|
|
|
|
|
|
|
Layout components use `computed` refs to prevent unnecessary re-renders:
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-25 08:40:12 -08:00
|
|
|
const sidebarCollapsed = computed(() => appStore.sidebarCollapsed)
|
2025-12-18 13:50:39 +08:00
|
|
|
// This only re-renders when sidebarCollapsed changes
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Troubleshooting
|
|
|
|
|
|
|
|
|
|
### Sidebar not showing
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- Check if `useAppStore` is properly initialized
|
|
|
|
|
- Verify Tailwind classes are being processed
|
|
|
|
|
- Check z-index conflicts with other components
|
|
|
|
|
|
|
|
|
|
### Routes not highlighting in sidebar
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- Ensure route paths match exactly
|
|
|
|
|
- Check `isActive()` function logic
|
|
|
|
|
- Verify `useRoute()` is working correctly
|
|
|
|
|
|
|
|
|
|
### User info not displaying
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- Ensure auth store is initialized with `checkAuth()`
|
|
|
|
|
- Verify user is logged in
|
|
|
|
|
- Check localStorage for auth data
|
|
|
|
|
|
|
|
|
|
### Mobile menu not working
|
2025-12-25 08:40:12 -08:00
|
|
|
|
2025-12-18 13:50:39 +08:00
|
|
|
- Verify `toggleSidebar()` is called correctly
|
|
|
|
|
- Check responsive breakpoints (md:)
|
|
|
|
|
- Test on actual mobile device or browser dev tools
|