404 Not Found

404 Not Found


nginx

تمرين شامل — الميزات الأساسية

في المرحلة 2، تعلمنا عن Pinia و Composable و Server API و Middleware و Plugins—حان وقت ربطها جميعاً. متطلبات Charlie: يجب أن تكون سلة التسوق مدمجة بالكامل من API عبر Store إلى الصفحة؛ يجب أن يكون لوحة تحكم Bob ضوابط وصول؛ ويجب أن تكون تجربة تسوق Alice سلسة ومتواصلة.

1. ما ستتعلمه


2. قصة حقيقية عن فريق

(1) نقطة الألم: الوحدات تعمل بشكل مستقل لكن لا يمكن دمجها

صفحة سلة التسوق الخاصة بـ Alice تعرض المنتجات، لكن البيانات تُفقد بعد التحديث. كتب Bob واجهة API، لكن الواجهة الأمامية لا تستدعيها. الوسيط مكتوب لكنه لم يُدمج. كل وحدة تعمل بشكل جيد بمفردها، لكن عند جمعها معاً، ينهار النظام.

(2) حل اختبار التكامل الشامل من طرف إلى طرف

دمج سلسلة البيانات بالكامل من API → Composable → Store → الصفحة، لضمان تدفق واضح للبيانات في كل مرحلة.

(3) الفوائد: تجربة تسوق كاملة

يمكن لـ Alice إضافة عناصر إلى سلة التسوق → العناصر تبقى بعد التحديث → الانتقال إلى صفحة الدفع → لوحة التحكم محمية بضوابط الوصول؛ العملية بالكامل سلسة.


3. بنية تعاون الوحدات الوظيفية الأساسية في المرحلة 2

100%
graph TB
    A[MegaShop المرحلة 2] --> B[طبقة Server API]
    A --> C[طبقة Composable]
    A --> D[طبقة الحالة - Pinia]
    A --> E[طبقة الوسائط]
    A --> F[طبقة الإضافات]

    B --> B1[/api/products CRUD]
    B --> B2[/api/cart CRUD]
    B --> B3[/api/auth تسجيل/دخول]

    C --> C1[useCart → Store + API]
    C --> C2[useProductFilter → API]
    C --> C3[usePriceFormat → نقي]

    D --> D1[cartStore → items/total]
    D --> D2[userStore → auth/profile]

    E --> E1[auth.global.ts → فحص تسجيل الدخول]
    E --> E2[admin.ts → فحص الدور]

    F --> F1[logger → تصحيح]
    F --> F2[stripe.client → الدفع]

    C1 --> D1
    C1 --> B2
    C2 --> B1

(1) مسار تدفق البيانات الكامل

العملية تدفق البيانات الوحدات المعنية
تصفح المنتجات API → useFetch → الصفحة Server API
إضافة إلى السلة API → useCart → cartStore → الصفحة API + Composable + Store
عرض السلة cartStore → الصفحة Store
الدفع cartStore → useCheckout → Stripe → API Store + Composable + Plugin + API
لوحة التحكم وسيط auth → وسيط admin → الصفحة Middleware

4. واجهة API كاملة لسلة التسوق

(1) ▶ مثال: واجهة API كاملة لسلة التسوق

TYPESCRIPT
// server/api/cart/index.get.ts
export default defineEventHandler((event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const cart = mockCarts.find(c => c.sessionId === sessionId)
  return cart || { sessionId, items: [], total: 0 }
})

الناتج:

TEXT
// تم التنفيذ بنجاح
TYPESCRIPT
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId, quantity = 1 } = await readBody(event)

  const product = mockProducts.find(p => p.id === productId)
  if (!product) throw createError({ statusCode: 404, message: 'Product not found' })

  let cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) {
    cart = { sessionId, items: [], total: 0 }
    mockCarts.push(cart)
  }

  const existing = cart.items.find(i => i.productId === productId)
  if (existing) {
    existing.quantity += quantity
  } else {
    cart.items.push({ productId, name: product.name, price: product.price, quantity, image: product.image })
  }
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  setCookie(event, 'session-id', sessionId, { httpOnly: true, maxAge: 86400 * 30 })
  return cart
})
TYPESCRIPT
// server/api/cart/remove.delete.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId } = await readBody(event)

  const cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) throw createError({ statusCode: 404, message: 'Cart not found' })

  cart.items = cart.items.filter(i => i.productId !== productId)
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  return cart
})

5. تكامل Pinia Store + Composable

(1) ▶ مثال: cartStore محسّن

TYPESCRIPT
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const isLoading = ref(false)

  const totalItems = computed(() => items.value.reduce((s, i) => s + i.quantity, 0))
  const totalPrice = computed(() => items.value.reduce((s, i) => s + i.price * i.quantity, 0))
  const formattedTotal = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(totalPrice.value)
  )

  async function fetchCart() {
    isLoading.value = true
    try {
      const cart = await $fetch('/api/cart')
      items.value = cart.items || []
    } finally {
      isLoading.value = false
    }
  }

  async function addItem(productId: number, quantity = 1) {
    const cart = await $fetch('/api/cart/add', {
      method: 'POST', body: { productId, quantity }
    })
    items.value = cart.items
  }

  async function removeItem(productId: number) {
    const cart = await $fetch('/api/cart/remove', {
      method: 'DELETE', body: { productId }
    })
    items.value = cart.items
  }

  function clearCart() {
    items.value = []
  }

  return { items, isLoading, totalItems, totalPrice, formattedTotal, fetchCart, addItem, removeItem, clearCart }
})

interface CartItem { productId: number; name: string; price: number; quantity: number; image: string }

الناتج:

TEXT
// تم التنفيذ بنجاح

(2) ▶ مثال: useCart Composable

TYPESCRIPT
// composables/useCart.ts
export function useCart() {
  const cartStore = useCartStore()
  const { $logger } = useNuxtApp()
  const { notify } = useNotification()

  async function addToCart(productId: number, productName: string) {
    try {
      await cartStore.addItem(productId)
      notify(`${productName} added to cart`, 'success')
      $logger.info('Item added to cart', { productId })
    } catch (err) {
      notify('Failed to add item', 'error')
      $logger.error('Add to cart failed', { productId, err })
    }
  }

  async function removeFromCart(productId: number) {
    try {
      await cartStore.removeItem(productId)
      notify('Item removed from cart', 'info')
    } catch (err) {
      notify('Failed to remove item', 'error')
    }
  }

  return {
    items: computed(() => cartStore.items),
    totalItems: computed(() => cartStore.totalItems),
    totalPrice: computed(() => cartStore.totalPrice),
    formattedTotal: computed(() => cartStore.formattedTotal),
    isLoading: computed(() => cartStore.isLoading),
    addToCart,
    removeFromCart
  }
}

الناتج:

TEXT
// تم التنفيذ بنجاح

6. تكوين وسيط الصلاحيات

(1) ▶ مثال: بنية وسيط كاملة

TYPESCRIPT
// middleware/01-auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
  const publicPaths = ['/', '/products', '/about', '/login', '/register']
  if (publicPaths.some(p => to.path === p || to.path.startsWith('/products'))) return

  const token = useCookie('auth-token')
  if (!token.value) return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
})

الناتج:

TEXT
// تم التنفيذ بنجاح
TYPESCRIPT
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
  const userCookie = useCookie('user-data')
  if (!userCookie.value) return navigateTo('/login')
  if ((userCookie.value as any).role !== 'admin') {
    throw createError({ statusCode: 403, message: 'Admin access required' })
  }
})

7. اختبار تكامل شامل من طرف إلى طرف للصفحات

(1) ▶ مثال: صفحة تفاصيل المنتج (تدفق بيانات كامل)

VUE
<!-- 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">{{ formattedPrice }}</p>
      <p>{{ product.reviewCount }} reviews</p>
      <button @click="handleAddToCart" :disabled="adding">
        {{ adding ? 'Adding...' : 'Add to Cart' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
const { formatted } = usePriceFormat(computed(() => product.value?.price || 0))
const formattedPrice = formatted

const { addToCart } = useCart()
const adding = ref(false)

async function handleAddToCart() {
  if (!product.value) return
  adding.value = true
  await addToCart(product.value.id, product.value.name)
  adding.value = false
}
</script>

الناتج:

TEXT
// تم التنفيذ بنجاح

8. قائمة فحص اختبار التكامل

عنصر الاختبار طريقة التحقق النتيجة المتوقعة
API متاح curl /api/products يُرجع JSON للمنتجات
مشاركة حالة Store إضافة عنصر في الصفحة الرئيسية → عرض الكمية في صفحة التفاصيل الكميات متطابقة
استدعاء Composable النقر على "أضف إلى السلة" نجاح: تمت الإضافة + إشعار
حماية الوسيط وصول غير مصادق إلى /admin إعادة توجيه إلى /login
الإضافة تعمل عرض وحدة التحكم يوجد مخرجات Logger
إرواء بيانات SSR عرض المصدر HTML يحتوي على بيانات المنتج
ثبات Cookie تحديث الصفحة بيانات سلة التسوق محفوظة

❓ أسئلة شائعة

س ماذا أفعل إذا كانت بيانات Store و API غير متزامنة؟
ج بعد أن يستدعي Store واجهة API عبر addItem، استخدم القيمة المُرجعة لتحديث مصفوفة items لضمان بقاء Store والخلفية متزامنين. لا تُعدِّل Store فقط دون استدعاء API.
س هل استخدام كل من Store و $fetch في Composable متكرر؟
ج لا. يدير Store حالة العميل، بينما $fetch يتزامن مع الخلفية. useCart يُغلِّف كليهما معاً، لذا لا يحتاج المستدعي للقلق بشأن التفاصيل.
س كل من وسيط الصفحة ووسيط API يعالجان المصادقة. هل سيؤدي هذا إلى تعارض؟
ج لا. وسيط الصفحة يحمي انتقالات المسارات (الواجهة الأمامية)، بينما وسيط الخادم يحمي طلبات API (الخلفية). الاثنان مكملان وضروريان معاً.
س كيف أقوم بتصحيح تدفق البيانات من طرف إلى طرف؟
ج استخدم لوحة Pinia في Nuxt DevTools لعرض حالة Store، ولوحة Network لعرض طلبات API، ووحدة التحكم لعرض مخرجات logger. استكشف المشاكل خطوة بخطوة، بدءاً من استجابة API → تحديث Store → عرض الصفحة.
س كيف تُهيَّأ بيانات سلة التسوق أثناء SSR؟
ج أثناء SSR، يُسترجع معرف الجلسة عبر cookie، وتُرجع API سلة التسوق بناءً على معرف الجلسة. بعد اكتمال إرواء العميل، يسترجع Store البيانات من حمولة API.
س متى ستُستبدل البيانات الوهمية في المرحلة 2 بقاعدة بيانات حقيقية؟
ج في الدرس 20 من المرحلة 4، سنستبدل البيانات الوهمية بـ Prisma. تم تصميم تنسيق واجهة API بالفعل؛ كل ما تبقى هو تعديل التنفيذ الداخلي لواجهة API للخادم.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة: ⭐): أكمل تكامل واجهة API لسلة التسوق مع cartStore بحيث يتحدث عدد سلة التسوق عند النقر على زر "أضف إلى السلة" في صفحة تفاصيل المنتج.
  2. تمرين متقدم (الصعوبة: ⭐⭐): نفِّذ نظام وسائط كامل: إذا وصل مستخدم غير مصادق إلى /checkout، أعد توجيهه إلى صفحة تسجيل الدخول؛ إذا وصل مستخدم عادي إلى /admin، أرجئ خطأ 403.
  3. تحدٍ (الصعوبة: ⭐⭐⭐): نفِّذ عملية تسوق من طرف إلى طرف: تصفح المنتجات → إضافة إلى السلة → عرض السلة → الدفع (محاكاة الدفع باستخدام إضافة Stripe)، مع ضمان اتساق البيانات خلال العملية بالكامل.

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%