تمرين شامل للمرحلة 1 — صفحة هبوط ShopMetrics
التمرين الشامل للمرحلة 1 هو "مراجعة نهائية" — يجمع كل ما تعلمته في الدروس الستة الأولى لتسليم صفحة هبوط حقيقية وعاملة.
1. ما ستتعلمه
- تصميم مخطط توجيه شامل
- بناء تخطيطات ومكونات Blade قابلة لإعادة الاستخدام
- إنشاء متحكم للتعامل مع منطق الصفحة
- دمج Tailwind CSS مع Alpine.js
- تهيئة البيئة واتصال قاعدة البيانات
2. قصة قبول المرحلة 1 لـ Bob
(1) المشكلة: حتى بعد إكمال الدرس 7، لا أستطيع بناء صفحة كاملة
أكملت Alice الدروس الستة الأولى، لكنها تفهم كل مفهوم بمفرده، وعند جمعها معًا تختلط الأمور — كيف يتصل التوجيه بالمتحكمات؟ كيف يمرر المتحكم البيانات إلى Blade؟ كيف تختار بين وراثة التخطيط والمكونات؟ قال Bob: "هذا هو الفرق بين تعلم السباحة والدخول فعليًا في الماء — شاهدت سبع دروس نظرية، لكنك لم تسبح 25 مترًا بعد."
(2) حلول التمرين الشامل
يربط هذا الدرس بين المسارات والعروض والمتحكمات ومكونات Blade وتهيئة البيئة لبناء صفحة هبوط ShopMetrics كاملة وعاملة من الصفر — هذا سيكون أول مشروع عملي لك.
# بعد هذا الدرس، ستملك:
# - صفحة هبوط بقسم رئيسي
# - صفحة تسعير ببطاقات خطط
# - تخطيط لوحة تحكم بشريط جانبي
# - مكونات Blade قابلة لإعادة الاستخدام
# - اتصال قاعدة بيانات عامل
(3) العائد
بعد إكمال التمرين الشامل، بنت Alice صفحة هبوط ShopMetrics بشكل مستقل، وجميع الصفحات الخمس اجتازت اختبار القبول. قال Bob: "الفرق بين 'فهمها' و'تنفيذها' هو تمرين شامل واحد فقط."
3. تصميم مخطط التوجيه
(1) تخطيط الصفحات
graph LR
A["/ (الرئيسية)"] --> B["/pricing (التسعير)"]
A --> C["/about (عن)"]
A --> D["/contact (اتصل)"]
A --> E["/dashboard (لوحة التحكم)"]
E --> F["/dashboard/shops"]
E --> G["/dashboard/analytics"]
(2) تعريفات المسارات
// routes/web.php
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::get('/about', [AboutController::class, 'index'])->name('about');
Route::get('/contact', [ContactController::class, 'create'])->name('contact.create');
Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
Route::middleware('auth')->prefix('dashboard')->name('dashboard.')->group(function () {
Route::get('/', [DashboardController::class, 'index'])->name('index');
Route::resource('shops', Dashboard\ShopController::class);
});
| الصفحة | المسار | المتحكم | البرمجية الوسيطة |
|---|---|---|---|
| الرئيسية | GET / | HomeController | بلا |
| التسعير | GET /pricing | PricingController | بلا |
| عن | GET /about | AboutController | بلا |
| اتصل | GET+POST /contact | ContactController | بلا |
| لوحة التحكم | GET /dashboard | DashboardController | auth |
(1) ▶ مثال: إنشاء جميع المتحكمات
php artisan make:controller HomeController
php artisan make:controller PricingController
php artisan make:controller AboutController
php artisan make:controller ContactController
php artisan make:controller Dashboard/DashboardController
php artisan make:controller Dashboard/ShopController --resource
الناتج:
# تم تنفيذ الأمر بنجاح
4. تصميم التخطيط والمكونات
(1) هيكل التخطيط
layouts/app.blade.php ← التخطيط الرئيسي (شريط تنقل + تذييل)
├── layouts/dashboard.blade.php ← تخطيط لوحة التحكم (شريط جانبي + محتوى)
│ └── dashboard/analytics.blade.php
└── home/index.blade.php ← صفحة الهبوط
(2) قائمة المكونات
| المكون | النوع | الملف | الغرض |
|---|---|---|---|
<x-navbar> |
مجهول | components/navbar.blade.php | شريط تنقل الموقع |
<x-footer> |
مجهول | components/footer.blade.php | تذييل الموقع |
<x-hero> |
مجهول | components/hero.blade.php | قسم Hero للصفحة الرئيسية |
<x-plan-card> |
مجهول | components/plan-card.blade.php | بطاقة تسعير |
<x-stat-card> |
مجهول | components/stat-card.blade.php | بطاقة إحصائيات لوحة التحكم |
<x-sidebar> |
مجهول | components/sidebar.blade.php | شريط جانبي للوحة التحكم |
(1) ▶ مثال: إنشاء مكونات Blade
# إنشاء ملفات المكونات يدويًا
mkdir -p resources/views/components
touch resources/views/components/navbar.blade.php
touch resources/views/components/footer.blade.php
touch resources/views/components/hero.blade.php
touch resources/views/components/plan-card.blade.php
touch resources/views/components/stat-card.blade.php
touch resources/views/components/sidebar.blade.php
الناتج:
# تم تنفيذ الأمر بنجاح
5. تنفيذ التخطيط والمكونات الأساسية
(1) التخطيط الرئيسي
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'ShopMetrics') — تحليلات التجارة الإلكترونية</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 text-gray-900">
<x-navbar />
<main>
@yield('content')
</main>
<x-footer />
</body>
</html>
(2) مكون شريط التنقل
<!-- resources/views/components/navbar.blade.php -->
<nav class="bg-white shadow-sm" x-data="{ open: false }">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<a href="{{ route('home') }}" class="flex items-center font-bold text-xl text-blue-600">
ShopMetrics
</a>
<div class="hidden md:flex items-center space-x-6">
<a href="{{ route('pricing') }}" class="text-gray-600 hover:text-gray-900">التسعير</a>
<a href="{{ route('about') }}" class="text-gray-600 hover:text-gray-900">عن</a>
@auth
<a href="{{ route('dashboard.index') }}" class="btn-primary">لوحة التحكم</a>
@else
<a href="{{ route('login') }}" class="text-gray-600 hover:text-gray-900">تسجيل الدخول</a>
<a href="{{ route('register') }}" class="btn-primary">إنشاء حساب</a>
@endauth
</div>
<button @click="open = !open" class="md:hidden">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path x-show="!open" d="M4 6h16M4 12h16M4 18h16"/>
<path x-show="open" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div x-show="open" class="md:hidden py-2 space-y-2">
<a href="{{ route('pricing') }}" class="block py-2 text-gray-600">التسعير</a>
@auth
<a href="{{ route('dashboard.index') }}" class="block py-2 btn-primary">لوحة التحكم</a>
@else
<a href="{{ route('login') }}" class="block py-2 text-gray-600">تسجيل الدخول</a>
<a href="{{ route('register') }}" class="block py-2 btn-primary">إنشاء حساب</a>
@endauth
</div>
</div>
</nav>
(3) مكون Hero
<!-- resources/views/components/hero.blade.php -->
@props(['title', 'subtitle', 'cta_text' => 'ابدأ الآن', 'cta_route' => 'register'])
<section class="bg-gradient-to-r from-blue-600 to-indigo-700 text-white py-20">
<div class="max-w-7xl mx-auto px-4 text-center">
<h1 class="text-4xl md:text-6xl font-bold mb-6">{{ $title }}</h1>
<p class="text-xl md:text-2xl mb-8 text-blue-100">{{ $subtitle }}</p>
<a href="{{ route($cta_route) }}" class="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition">
{{ $cta_text }}
</a>
</div>
</section>
(1) ▶ مثال: مكون بطاقة التسعير
<!-- resources/views/components/plan-card.blade.php -->
@props(['plan', 'popular' => false])
<div class="bg-white rounded-xl shadow-lg p-8 {{ $popular ? 'ring-2 ring-blue-500 relative' : '' }}">
@if($popular)
<span class="absolute -top-3 left-1/2 -translate-x-1/2 bg-blue-500 text-white px-4 py-1 rounded-full text-sm font-semibold">
الأكثر شعبية
</span>
@endif
<h3 class="text-xl font-bold mb-2">{{ $plan['name'] }}</h3>
<div class="mb-6">
<span class="text-4xl font-bold">${{ $plan['price'] }}</span>
<span class="text-gray-500">/شهر</span>
</div>
<ul class="space-y-3 mb-8">
@foreach($plan['features'] as $feature)
<li class="flex items-center">
<svg class="w-5 h-5 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/>
</svg>
{{ $feature }}
</li>
@endforeach
</ul>
<a href="{{ route('register') }}" class="{{ $popular ? 'btn-primary' : 'btn-secondary' }} w-full text-center block">
اختر {{ $plan['name'] }}
</a>
</div>
الناتج:
// تم التنفيذ بنجاح
6. تنفيذ المتحكم
(1) ▶ مثال: HomeController وPricingController
// app/Http/Controllers/HomeController.php
class HomeController extends Controller
{
public function index(): View
{
$features = [
['icon' => 'chart', 'title' => 'تحليلات فورية', 'desc' => 'تتبع المبيعات والطلبات والإيرادات في الوقت الفعلي.'],
['icon' => 'users', 'title' => 'متعدد المستأجرين', 'desc' => 'بيانات منفصلة لكل متجر إلكتروني.'],
['icon' => 'bell', 'title' => 'تنبيهات ذكية', 'desc' => 'احصل على إشعار عند تغير المؤشرات الرئيسية.'],
];
return view('home.index', compact('features'));
}
}
// app/Http/Controllers/PricingController.php
class PricingController extends Controller
{
public function index(): View
{
$plans = [
['name' => 'Starter', 'price' => 29, 'features' => ['5 متاجر', '1K طلب/شهر', 'تحليلات أساسية', 'دعم بالبريد']],
['name' => 'Pro', 'price' => 79, 'features' => ['25 متجر', '10K طلب/شهر', 'تحليلات متقدمة', 'دعم ذو أولوية', 'وصول API']],
['name' => 'Enterprise', 'price' => 199, 'features' => ['متاجر غير محدودة', 'طلبات غير محدودة', 'تحليلات مخصصة', 'دعم مخصص', 'API + Webhooks', 'SSO']],
];
return view('pricing.index', compact('plans'));
}
}
الناتج:
// تم التنفيذ بنجاح
(2) ▶ مثال: DashboardController
// app/Http/Controllers/Dashboard/DashboardController.php
class DashboardController extends Controller
{
public function index(): View
{
$user = auth()->user();
$tenant = $user->tenant;
$stats = [
['label' => 'إجمالي الإيرادات', 'value' => '$' . number_format($tenant->revenue ?? 0), 'trend' => 12.5],
['label' => 'المتاجر النشطة', 'value' => $tenant->shops()->count(), 'trend' => 3.2],
['label' => 'طلبات اليوم', 'value' => $tenant->orders()->today()->count(), 'trend' => -2.1],
];
return view('dashboard.index', compact('tenant', 'stats'));
}
}
الناتج:
// تم التنفيذ بنجاح
7. تهيئة البيئة وقاعدة البيانات
# .env — تهيئة MySQL لـ ShopMetrics
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=shopmetrics
DB_USERNAME=root
DB_PASSWORD=secret
# توليد مفتاح التطبيق وتشغيل التهجيرات
php artisan key:generate
php artisan migrate
# تثبيت Breeze لهيكل المصادقة
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run build
| الخطوة | الأمر | الوظيفة |
|---|---|---|
| 1 | key:generate |
توليد APP_KEY |
| 2 | migrate |
إنشاء جداول البيانات |
| 3 | breeze:install |
توليد صفحة تسجيل الدخول/الإنشاء |
| 4 | npm run build |
تجميع أصول الواجهة الأمامية |
8. مثال شامل: صفحة هبوط ShopMetrics الرئيسية
<!-- ============================================
شامل: صفحة هبوط ShopMetrics
يغطي: التخطيط، المكونات، المسارات، تمرير البيانات
============================================ -->
<!-- resources/views/home/index.blade.php -->
@extends('layouts.app')
@section('title', 'ShopMetrics — منصة تحليلات التجارة الإلكترونية')
@section('content')
<!-- قسم Hero -->
<x-hero
title="تتبع أداء تجارتك الإلكترونية"
subtitle="تحليلات فورية لمنصات التجارة الإلكترونية متعددة المستأجرين. راقب الإيرادات والطلبات ورؤى العملاء — كل ذلك في لوحة تحكم واحدة."
cta_text="ابدأ تجربة مجانية"
cta_route="register"
/>
<!-- قسم الميزات -->
<section class="py-20 bg-white">
<div class="max-w-7xl mx-auto px-4">
<h2 class="text-3xl font-bold text-center mb-12">لماذا ShopMetrics؟</h2>
<div class="grid md:grid-cols-3 gap-8">
@foreach($features as $feature)
<div class="text-center p-6">
<div class="w-12 h-12 bg-blue-100 rounded-lg mx-auto mb-4 flex items-center justify-center">
<span class="text-blue-600 text-2xl">
@if($feature['icon'] === 'chart')📈@elseif($feature['icon'] === 'users')👥@else🔔@endif
</span>
</div>
<h3 class="text-xl font-semibold mb-2">{{ $feature['title'] }}</h3>
<p class="text-gray-600">{{ $feature['desc'] }}</p>
</div>
@endforeach
</div>
</div>
</section>
<!-- قسم CTA -->
<section class="py-16 bg-gray-100">
<div class="max-w-4xl mx-auto text-center px-4">
<h2 class="text-3xl font-bold mb-4">مستعد لتنمية أعمالك؟</h2>
<p class="text-gray-600 mb-8">انضم إلى أكثر من 500 تاجر يثقون في ShopMetrics لتحليلاتهم.</p>
<div class="flex gap-4 justify-center">
<a href="{{ route('register') }}" class="btn-primary">ابدأ تجربة مجانية</a>
<a href="{{ route('pricing') }}" class="btn-secondary">عرض التسعير</a>
</div>
</div>
</section>
@endsection
❓ أسئلة شائعة
npm run dev (إعادة تحميل ساخنة في وضع التطوير) أو npm run build (بناء الإنتاج)، وتحقق من أن تهيئة content في tailwind.config.js تتضمن مسارات جميع ملفات Blade.x-show في Alpine.js هو نفسه v-show في Vue؟v-show في Vue يتطلب نسخة Vue. استخدم Alpine.js للتفاعلات البسيطة وVue لتطبيقات الصفحة الواحدة المعقدة.📖 ملخص
- مع مخطط التوجيه، تخطط للصفحات أولًا، ثم تحدد المتحكمات والبرمجية الوسيطة
- التخطيطات تتعامل مع هيكل مستوى الصفحة، بينما مكونات Blade تتعامل مع مقاطع UI القابلة لإعادة الاستخدام
- أبقِ دوال المتحكم بسيطة؛ استخرج المنطق المعقد إلى صنف Service
- Tailwind CSS يُجمّع باستخدام Vite، وAlpine.js يتعامل مع التفاعلات البسيطة
- Breeze يوفر صفحات مصادقة جاهزة للاستخدام
- استخدم .env لتهيئة البيئة؛ استخدم وضع التصحيح لبيئة التطوير
📝 تمارين
-
مهمة أساسية (⭐): أكمل الصفحات الخمس لصفحة هبوط ShopMetrics (الرئيسية/التسعير/عن/اتصل/لوحة التحكم)، مع التأكد من إمكانية الوصول إلى جميع المسارات، ووراثة التخطيط تعمل بشكل صحيح، وروابط شريط التنقل صحيحة.
-
تمرين متقدم (⭐⭐): أضف تبديل دفع شهري/سنوي إلى صفحة التسعير (باستخدام Alpine.js). يجب أن يكون السعر السنوي بخصم 20%، وسعر البطاقة ينتقل برسوم متحركة عند التبديل بين خيارات الدفع.
-
تحدٍ (⭐⭐⭐): نفذ قائمة ديناميكية في شريط لوحة التحكم الجانبي — اعرض عناصر قائمة مختلفة بناءً على دور المستخدم (admin/tenant_owner/analyst)، باستخدام View Composer لاسترداد تهيئات الصلاحيات من قاعدة البيانات.



