تطوير المشروع
بمجرد اكتمال التصميم، قاد Charlie الفريق للبدء في كتابة الكود. بدأت وحدة المصادقة أولاً—لا دخول، لا وصول. ثم جاءت وحدة المنتجات—عمليات CRUD وتخزين ISR المؤقت لملايين المنتجات. وتبعتها وحدتا سلة التسوق والطلبات. اختبر Alice تدفق العميل، بينما تحقق Bob من الوظائف الإدارية؛ خضعت كل وحدة لاختبار شامل.
1. ما ستتعلمه
- وحدة المصادقة: OAuth2 + JWT + وسيط المصادقة + صلاحيات قائمة على الأدوار
- وحدة المنتجات: ملايين عمليات CRUD + تخزين ISR المؤقت + SEO + بحث وتصفية
- وحدة سلة التسوق: Pinia Store + Composable + واجهة API للخادم + التحقق من المخزون
- وحدة الطلبات: معاملات Prisma + آلة الحالة + دفع Stripe
- التدويل والأداء: i18n بثلاث لغات + @nuxt/image + تدقيق Lighthouse
2. قصة حقيقية عن فريق
(1) نقطة الألم: خطط التصميم لا يمكن تنفيذها
لدى Charlie مجموعة كاملة من وثائق التصميم، لكن الفريق لا يعرف من أين يبدأ وماذا يفعل بعد ذلك. كتب Alice صفحات المنتجات، لكن سلة التسوق لا تملك واجهة API بعد؛ كتب Bob لوحة الإدارة، لكن المصادقة لم تُعد بعد. الوحدات تنتظر بعضها البعض، وتعطل التقدم.
(2) حل للتطوير التدريجي حسب الوحدات
طوّر كل وحدة بترتيب التبعية: المصادقة → المنتجات → سلة التسوق → الطلبات → التدويل. اختبر كل وحدة فور اكتمالها، دون الاعتماد على وحدات لم تُنتهى بعد.
(3) النتائج: تقدم ثابت
تستغرق كل وحدة 3-5 أيام للإكمال، وستُطلق جميع الميزات الأساسية خلال أسبوعين، وعندها ستتمكن Alice من إكمال عملية التسوق بالكامل.
3. معالم التطوير
(1) مخطط جانت لتطوير الوحدات
gantt
title معالم تطوير MegaShop
dateFormat YYYY-MM-DD
section وحدة المصادقة
JWT + OAuth2 :a1, 2026-01-01, 3d
وسيط المصادقة :a2, after a1, 2d
section وحدة المنتجات
CRUD API :p1, after a2, 3d
بحث + تصفية :p2, after p1, 2d
تخزين ISR مؤقت :p3, after p2, 1d
section وحدة السلة
Store + API :c1, after a2, 3d
Composable :c2, after c1, 1d
section وحدة الطلبات
معاملة Prisma :o1, after c1, 3d
دفع Stripe :o2, after o1, 2d
section i18n + أداء
ثلاث لغات :i1, after p3, 2d
تحسين الصور :i2, after i1, 1d
تدقيق Lighthouse :i3, after i2, 1d
4. وحدة المصادقة
(1) ▶ مثال: أداة JWT + واجهة API للتسجيل/تسجيل الدخول
// server/utils/jwt.ts
import jwt from 'jsonwebtoken'
const config = useRuntimeConfig()
export function signTokenPair(payload: { userId: number; email: string; role: string }) {
return {
accessToken: jwt.sign(payload, config.jwtAccessSecret, { expiresIn: '15m' }),
refreshToken: jwt.sign(payload, config.jwtRefreshSecret, { expiresIn: '7d' })
}
}
export function setAuthCookies(event: any, tokens: { accessToken: string; refreshToken: string }) {
setCookie(event, 'access-token', tokens.accessToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 900 })
setCookie(event, 'refresh-token', tokens.refreshToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 604800 })
}
الناتج:
// التنفيذ ناجح
// server/api/auth/register.post.ts
export default defineEventHandler(async (event) => {
const { email, password, name } = await readBody(event)
const existing = await prisma.user.findUnique({ where: { email } })
if (existing) throw createError({ statusCode: 409, message: 'Email already registered' })
const hashedPassword = await hashPassword(password)
const user = await prisma.user.create({
data: { email, name, password: hashedPassword, role: 'CUSTOMER' }
})
const tokens = signTokenPair({ userId: user.id, email: user.email, role: user.role })
setAuthCookies(event, tokens)
return { user: { id: user.id, email: user.email, name: user.name, role: user.role } }
})
5. وحدة المنتجات
(1) ▶ مثال: واجهة CRUD API للمنتجات (Prisma + تخزين مؤقت)
// server/api/products/index.get.ts
export default cachedEventHandler(
async (event) => {
const { page = '1', limit = '20', category, search, sort } = getQuery(event)
const where = {
AND: [
category ? { category: { slug: category as string } } : {},
search ? { OR: [
{ name: { contains: search as string, mode: 'insensitive' } },
{ description: { contains: search as string, mode: 'insensitive' } }
]} : {}
]
}
const orderBy = sort === 'price-asc' ? { price: 'asc' }
: sort === 'price-desc' ? { price: 'desc' }
: { createdAt: 'desc' }
const [items, total] = await Promise.all([
prisma.product.findMany({
where, orderBy,
skip: (Number(page) - 1) * Number(limit),
take: Number(limit),
include: { category: { select: { name: true, slug: true } } }
}),
prisma.product.count({ where })
])
return { items, total, page: Number(page), limit: Number(limit) }
},
{ maxAge: 60, swr: true, getKey: (event) => `products:${getRequestURL(event).search}` }
)
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: تفاصيل المنتج + SEO
<!-- pages/products/[id].vue -->
<template>
<div v-if="product">
<h1>{{ product.name }}</h1>
<p>{{ formatFromUSD(product.price) }}</p>
<button @click="handleAddToCart">{{ t('product.addToCart') }}</button>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const config = useRuntimeConfig()
const { t } = useI18n()
const { formatFromUSD } = useLocalizedPrice()
const { addToCart } = useCart()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
// SEO ديناميكي لكل منتج
useSeoMeta({
title: () => `${product.value?.name} - MegaShop`,
description: () => `Buy ${product.value?.name} - $${product.value?.price} USD`,
ogTitle: () => product.value?.name,
ogImage: () => product.value?.image,
ogType: 'product'
})
// بيانات منظمة JSON-LD
useHead({ script: [{ type: 'application/ld+json', innerHTML: computed(() => JSON.stringify({
'@context': 'https://schema.org', '@type': 'Product',
name: product.value?.name,
offers: { '@type': 'Offer', priceCurrency: 'USD', price: product.value?.price }
}))}]})
async function handleAddToCart() {
if (product.value) await addToCart(product.value.id, product.value.name)
}
</script>
الناتج:
// التنفيذ ناجح
6. وحدة سلة التسوق
(1) ▶ مثال: cartStore + useCart
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
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))
async function addItem(productId: number) {
const cart = await $fetch('/api/cart/add', { method: 'POST', body: { productId } })
items.value = cart.items
}
async function removeItem(productId: number) {
const cart = await $fetch('/api/cart/remove', { method: 'DELETE', body: { productId } })
items.value = cart.items
}
async function fetchCart() {
const cart = await $fetch('/api/cart')
items.value = cart.items || []
}
return { items, totalItems, totalPrice, addItem, removeItem, fetchCart }
})
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: واجهة API لسلة التسوق (مع التحقق من المخزون)
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
const userId = event.context.user?.id
const { productId, quantity = 1 } = await readBody(event)
// التحقق من المخزون
const product = await prisma.product.findUnique({ where: { id: productId } })
if (!product?.inStock) throw createError({ statusCode: 400, message: 'Product out of stock' })
// Upsert عنصر السلة
await prisma.cartItem.upsert({
where: { userId_productId: { userId, productId } },
update: { quantity: { increment: quantity } },
create: { userId, productId, quantity }
})
const cart = await prisma.cartItem.findMany({
where: { userId },
include: { product: { select: { name: true, price: true, image: true } } }
})
const total = cart.reduce((s, i) => s + Number(i.product.price) * i.quantity, 0)
return { items: cart, total }
})
الناتج:
// التنفيذ ناجح
7. وحدة الطلبات
(1) ▶ مثال: معاملة إنشاء الطلب
// server/api/orders/index.post.ts
export default defineEventHandler(async (event) => {
const userId = event.context.user?.id
if (!userId) throw createError({ statusCode: 401, message: 'Login required' })
const order = await prisma.$transaction(async (tx) => {
// جلب عناصر السلة
const cartItems = await tx.cartItem.findMany({
where: { userId },
include: { product: true }
})
if (cartItems.length === 0) {
throw createError({ statusCode: 400, message: 'Cart is empty' })
}
// التحقق من المخزون لجميع العناصر
for (const item of cartItems) {
if (!item.product.inStock) {
throw createError({ statusCode: 400, message: `${item.product.name} out of stock` })
}
}
// حساب الإجمالي
const total = cartItems.reduce((sum, item) => sum + Number(item.product.price) * item.quantity, 0)
// إنشاء الطلب مع العناصر
const newOrder = await tx.order.create({
data: {
userId,
total,
status: 'PENDING',
items: {
create: cartItems.map(item => ({
productId: item.productId,
quantity: item.quantity,
price: item.product.price
}))
}
},
include: { items: { include: { product: true } } }
})
// مسح السلة
await tx.cartItem.deleteMany({ where: { userId } })
return newOrder
})
return { order, message: 'Order created' }
})
الناتج:
// التنفيذ ناجح
8. التدويل والأداء
(1) ▶ مثال: تكوين إنتاج كامل في nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
modules: [
'@pinia/nuxt',
'@nuxtjs/tailwindcss',
'@nuxtjs/i18n',
'@nuxtjs/sitemap',
'@nuxt/image',
'~/modules/analytics'
],
app: {
head: {
title: 'MegaShop',
titleTemplate: '%s | MegaShop',
meta: [
{ name: 'description', content: 'Over 1 million products shipped worldwide' }
]
}
},
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json', currency: 'USD' },
{ code: 'zh', name: 'Chinese', file: 'zh.json', currency: 'CNY' },
{ code: 'ja', name: 'Japanese', file: 'ja.json', currency: 'JPY' }
],
defaultLocale: 'en',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default'
},
image: {
quality: 80,
format: ['webp', 'avif']
},
sitemap: {
hostname: 'https://megashop.com',
gzip: true
},
runtimeConfig: {
databaseUrl: process.env.DATABASE_URL,
redisUrl: process.env.REDIS_URL,
jwtAccessSecret: process.env.JWT_ACCESS_SECRET,
jwtRefreshSecret: process.env.JWT_REFRESH_SECRET,
public: {
apiBase: process.env.API_BASE || '/api',
siteUrl: process.env.SITE_URL || 'https://megashop.com',
stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY
}
},
routeRules: {
'/': { prerender: true },
'/about': { prerender: true },
'/products': { swr: 3600 },
'/products/**': { swr: 86400 },
'/categories/**': { swr: 21600 },
'/admin/**': { ssr: false },
'/cart': { ssr: false },
'/checkout': { ssr: false },
'/profile/**': { ssr: false },
'/api/**': { cors: true },
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } }
},
nitro: {
preset: process.env.DEPLOY_TARGET || 'node-server',
storage: {
products: { driver: process.env.NODE_ENV === 'production' ? 'redis' : 'memory' }
}
},
components: [{ path: '~/components', pathPrefix: false }]
})
الناتج:
// التنفيذ ناجح
❓ أسئلة شائعة
shared/types.ts. الواجهة الأمامية والخلفية تشترك في تعريفات الأنواع هذه، ويتم التحقق من الاتساق في وقت التجميع.prisma.findMany()؛ يبقى المسار وتنسيقات الاستجابة كما هي.📖 ملخص
- التطوير بترتيب التبعية: المصادقة → المنتجات → سلة التسوق → الطلبات → التدويل
- وحدة المصادقة: رموز JWT المزدوجة + ملف تعريف ارتباط httpOnly + وسيط المصادقة + فحص دور المسؤول
- وحدة المنتجات: Prisma CRUD + تخزين Nitro المؤقت + ISR + علامات meta لـ SEO + JSON-LD
- سلة التسوق: Pinia Store + useCart Composable + التحقق من المخزون
- الطلبات: معاملات Prisma تضمن الذرية + تكامل دفع Stripe
📝 تمارين
- تمرين أساسي (الصعوبة: ⭐): تنفيذ وحدة مصادقة (تسجيل/تسجيل دخول/تحديث token)، والتحقق من أن ملف تعريف الارتباط معيّن بشكل صحيح
- تمرين متقدم (الصعوبة: ⭐⭐): تنفيذ سير عمل CRUD كامل للمنتجات، بما في ذلك سلة التسوق والدفع، كتطبيق شامل من النهاية إلى النهاية.
- تحدي (الصعوبة: ⭐⭐⭐): إكمال جميع الوحدات الأساسية لـ MegaShop + i18n + تحسين الأداء، مع تحقيق درجة Lighthouse تبلغ 90+
---|



