تمارين شاملة — البداية
الآن بعد أن أكملنا الدروس الخمسة في المرحلة الأولى، حان الوقت لربط كل شيء معًا. طلب Charlie من الفريق بناء نسخة أساسية عاملة من MegaShop—بما في ذلك صفحة رئيسية وصفحات فئات وصفحات تفاصيل منتجات، بالإضافة إلى الجلب المسبق لـ SSR وتبديل التخطيط السليم. ستقوم Alice بدور الزبونة لاختبار تجربة المستخدم.
1. ما ستتعلمه
- بناء إطار صفحات MegaShop كامل
- تنفيذ التبديل التلقائي بين تخطيطي "default" و "sidebar"
- استخدام useFetch لتحميل بيانات المنتجات مسبقًا من API وتصييرها
- معالجة معلمات التوجيه الديناميكية وصفحات 404
- مراجعة الكود: صحة إماهة بيانات SSR والاستيراد التلقائي
2. قصة حقيقية عن فريق
(1) نقطة الألم: عدم القدرة على دمج المعرفة المتباينة
تعلمت Alice عن التوجيه والمكونات وجلب البيانات، لكنها لا تعرف كيف تجمعها في مشروع كامل. صفحة Bob ليس لها تخطيط، والصفحة تومض بعد تحميل بيانات المنتج. يحتاج Charlie إلى MVP لعرضه على المستثمرين.
(2) حلول شاملة لمشاكل العالم الحقيقي
ادمج كل المفاهيم من المرحلة الأولى في مشروع واحد: توجيه الملفات + نظام التخطيط + الجلب المسبق لـ SSR بـ useFetch + الاستيراد التلقائي للمكونات، وابنِ منصة تجارة إلكترونية عاملة من الصفر.
(3) الفوائد: من الأجزاء إلى المنتجات
بنهاية هذا الفصل، ستملك Alice نسخة أساسية عاملة من MegaShop—بأربع صفحات وتخطيطين وتصيير من جانب الخادم (SSR) مع بيانات منتجات مُحضّرة مسبقًا ومخرجات HTML صديقة لـ SEO.
3. تخطيط المشروع
(1) بنية نسخة MegaShop الأساسية
flowchart TB
A[MegaShop Phase 1] --> B[Pages]
A --> C[Layouts]
A --> D[Components]
A --> E[API Routes]
B --> B1[/ → Homepage]
B --> B2[/products → Product List]
B --> B3[/products/[id] → Product Detail]
B --> B4[/about → About Page]
C --> C1[default → Header + Footer]
C --> C2[sidebar → Admin Sidebar]
D --> D1[AppHeader]
D --> D2[AppFooter]
D --> D3[ProductCard]
E --> E1[GET /api/products]
E --> E2[GET /api/products/:id]
(2) تصميم الصفحات والمسارات
| URL | ملف الصفحة | التخطيط | الميزات |
|---|---|---|---|
| / | pages/index.vue | default | الرئيسية: منتجات مميزة |
| /products | pages/products/index.vue | default | قائمة المنتجات (جلب مسبق SSR) |
| /products/[id] | pages/products/[id].vue | default | تفاصيل المنتج (جلب مسبق SSR) |
| /about | pages/about.vue | default | صفحة حول |
| /admin | pages/admin/index.vue | sidebar | لوحة الإدارة |
| 404 | Page Not Found | default | 404 مخصص |
4. بناء هيكل الصفحات
(1) ▶ مثال: المكون الجذري app.vue
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
الناتج:
// Execution Successful
(2) ▶ مثال: التخطيط الافتراضي
<!-- layouts/default.vue -->
<template>
<div class="layout-default">
<AppHeader />
<main class="container">
<slot />
</main>
<AppFooter />
</div>
</template>
الناتج:
// Execution Successful
(3) ▶ مثال: تخطيط الشريط الجانبي
<!-- layouts/sidebar.vue -->
<template>
<div class="layout-sidebar">
<AppHeader />
<div class="main-wrapper">
<aside class="sidebar">
<h3>Admin Panel</h3>
<nav>
<NuxtLink to="/admin">Dashboard</NuxtLink>
<NuxtLink to="/admin/products">Products</NuxtLink>
<NuxtLink to="/admin/orders">Orders</NuxtLink>
</nav>
</aside>
<main class="content">
<slot />
</main>
</div>
</div>
</template>
الناتج:
// Execution Successful
5. مسارات API الأساسية
(1) ▶ مثال: API قائمة المنتجات
// server/api/products/index.get.ts
interface Product {
id: number; name: string; price: number
image: string; category: string; inStock: boolean
}
const mockProducts: Product[] = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `Product ${i + 1}`,
price: Math.round(Math.random() * 500 * 100) / 100,
image: `/images/product-${i + 1}.webp`,
category: ['electronics', 'clothing', 'home'][i % 3],
inStock: Math.random() > 0.2
}))
export default defineEventHandler((event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 12
const category = query.category as string
let filtered = mockProducts
if (category) filtered = filtered.filter(p => p.category === category)
const start = (page - 1) * limit
return {
items: filtered.slice(start, start + limit),
total: filtered.length,
page,
limit
}
})
الناتج:
// Execution Successful
(2) ▶ مثال: API تفاصيل المنتج
// server/api/products/[id].get.ts
export default defineEventHandler((event) => {
const id = Number(getRouterParam(event, 'id'))
const product = {
id,
name: `Premium Product ${id}`,
price: 299.99,
image: `/images/product-${id}.webp`,
category: 'electronics',
inStock: true,
description: `High-quality product #${id} with premium features.`,
reviewCount: Math.floor(Math.random() * 5 * 1000),
rating: 4.5
}
if (id > 100) {
throw createError({ statusCode: 404, message: 'Product not found' })
}
return product
})
الناتج:
// Execution Successful
6. تنفيذ الصفحات
(1) ▶ مثال: الجلب المسبق لـ SSR للصفحة الرئيسية
<!-- pages/index.vue -->
<template>
<div class="homepage">
<section class="hero">
<h1>MegaShop</h1>
<p>Over 1 million products, shipped worldwide</p>
<NuxtLink to="/products" class="cta">Shop Now</NuxtLink>
</section>
<section class="featured">
<h2>Featured Products</h2>
<div class="grid">
<ProductCard v-for="p in featured" :key="p.id" :product="p" />
</div>
</section>
</div>
</template>
<script setup lang="ts">
const { data: featured } = await useFetch('/api/products', {
query: { limit: 6 },
transform: (data: any) => data.items
})
useHead({ title: 'MegaShop - Premium E-Commerce' })
</script>
الناتج:
// Execution Successful
(2) ▶ مثال: صفحة تفاصيل المنتج
<!-- pages/products/[id].vue -->
<template>
<div v-if="product" class="product-detail">
<img :src="product.image" :alt="product.name" />
<div class="info">
<h1>{{ product.name }}</h1>
<p class="price">${{ product.price }} USD</p>
<p>{{ product.description }}</p>
<p>{{ product.reviewCount }} reviews · {{ product.rating }} stars</p>
<button @click="addToCart">Add to Cart</button>
</div>
</div>
<div v-else class="not-found">
<h1>Product Not Found</h1>
<NuxtLink to="/products">Back to Products</NuxtLink>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
useHead({
title: computed(() => product.value ? `${product.value.name} - MegaShop` : 'Not Found')
})
const cart = useState<any[]>('cart', () => [])
function addToCart() {
if (product.value) cart.value.push(product.value)
}
</script>
الناتج:
// Execution Successful
7. قائمة مراجعة الكود
(1) فحص إماهة بيانات SSR
| عنصر الفحص | الطريقة الصحيحة | الأخطاء الشائعة |
|---|---|---|
| استخدام useFetch | await useFetch() انتظار البيانات |
نسيان await يؤدي لحالة pending |
| عرض مصدر SSR | HTML يحتوي على بيانات المنتجات | فقط <div> فارغ |
| تمرير Payload | العميل لا يكرر الطلبات | طلبات API مكررة في Network |
| route.params | استرجاع في المستوى الأعلى من setup | استرجاع في onMounted (هذا الخطاف غير متاح في SSR) |
(2) فحص صحة الاستيراد التلقائي
| عنصر الفحص | الطريقة الصحيحة | الأخطاء الشائعة |
|---|---|---|
| الإشارة للمكونات | ProductCard استيراد تلقائي | استيراد المكونات يدويًا |
| Composable | useFetch() استخدام مباشر | استيراد useFetch يدويًا |
| useState | useState('key') استخدام مباشر | استيراد يدوي من 'nuxt/app' |
| اسم المكون | إشارة بناءً على قواعد بادئة المجلد | اسم المكون لا يتطابق مع مسار الملف |
8. مثال شامل: nuxt.config.ts لنسخة MegaShop الأساسية
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
app: {
head: {
title: 'MegaShop',
meta: [
{ name: 'description', content: 'Premium e-commerce with over 1 million products' }
]
}
},
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
components: [{ path: '~/components', pathPrefix: false }],
routeRules: {
'/': { prerender: true },
'/products': { swr: 60 },
'/products/**': { swr: 3600 },
'/admin/**': { ssr: false }
}
})
❓ أسئلة شائعة
prerender: true يُولّد HTML ثابت أثناء البناء، لذلك لن تُحدّث البيانات تلقائيًا. ستحتاج لإعادة البناء أو التبديل لاستراتيجية swr.await. إذا كان useFetch داخل عبارة شرطية، قد يتخطاه SSR. تأكد من استدعائه في المستوى الأعلى من setup.createError({ statusCode: 404 }) على مستوى API.<Transition> لإضافة حركة انتقال، أو استخدام إعداد pageTransition.📖ملخص
- نسخة MegaShop الأساسية تتضمن أربع صفحات أساسية: الصفحة الرئيسية وقائمة المنتجات وتفاصيل المنتجات وصفحة "حول"
- تخطيطان: "default" (الواجهة الأمامية) و "sidebar" (لوحة الإدارة)، يمكن تبديلهما باستخدام definePageMeta
- الجلب المسبق لـ SSR بـ useFetch يضمن تضمين بيانات المنتجات في HTML، مما يجعله صديقًا لـ SEO
- API الخادم يستخدم بيانات وهمية؛ سيتم استخدام Prisma في المراحل اللاحقة
- تحقق من SSR: تأكد أن المصدر يحتوي على البيانات، وأنه لا توجد طلبات مكررة في تبويب Network، وأن الاستيراد التلقائي لا يُرجع أخطاء
📝تمارين
- تمرين أساسي (الصعوبة: ⭐): أكمل جميع صفحات نسخة MegaShop الأساسية وتأكد من إمكانية الوصول لجميع المسارات.
- تمرين متقدم (الصعوبة: ⭐⭐): أضف ميزة فلتر فئات المنتجات، واستخدم watch و useFetch في صفحة قائمة المنتجات لتحديث الصفحة تلقائيًا عند تبديل الفئات.
- تحدٍ (الصعوبة: ⭐⭐⭐): أضف صفحة admin/products (تخطيط sidebar + وضع CSR)، نفّذ عرض جدول المنتجات، وقارن الفروقات بين صفحات CSR و SSR عند عرض المصدر.
---|



