404 Not Found

404 Not Found


nginx

تمرين شامل للمرحلة 1 — صفحة هبوط ShopMetrics

التمرين الشامل للمرحلة 1 هو "مراجعة نهائية" — يجمع كل ما تعلمته في الدروس الستة الأولى لتسليم صفحة هبوط حقيقية وعاملة.

1. ما ستتعلمه


2. قصة قبول المرحلة 1 لـ Bob

(1) المشكلة: حتى بعد إكمال الدرس 7، لا أستطيع بناء صفحة كاملة

أكملت Alice الدروس الستة الأولى، لكنها تفهم كل مفهوم بمفرده، وعند جمعها معًا تختلط الأمور — كيف يتصل التوجيه بالمتحكمات؟ كيف يمرر المتحكم البيانات إلى Blade؟ كيف تختار بين وراثة التخطيط والمكونات؟ قال Bob: "هذا هو الفرق بين تعلم السباحة والدخول فعليًا في الماء — شاهدت سبع دروس نظرية، لكنك لم تسبح 25 مترًا بعد."

(2) حلول التمرين الشامل

يربط هذا الدرس بين المسارات والعروض والمتحكمات ومكونات Blade وتهيئة البيئة لبناء صفحة هبوط ShopMetrics كاملة وعاملة من الصفر — هذا سيكون أول مشروع عملي لك.

BASH
# بعد هذا الدرس، ستملك:
# - صفحة هبوط بقسم رئيسي
# - صفحة تسعير ببطاقات خطط
# - تخطيط لوحة تحكم بشريط جانبي
# - مكونات Blade قابلة لإعادة الاستخدام
# - اتصال قاعدة بيانات عامل

(3) العائد

بعد إكمال التمرين الشامل، بنت Alice صفحة هبوط ShopMetrics بشكل مستقل، وجميع الصفحات الخمس اجتازت اختبار القبول. قال Bob: "الفرق بين 'فهمها' و'تنفيذها' هو تمرين شامل واحد فقط."


3. تصميم مخطط التوجيه

(1) تخطيط الصفحات

100%
graph LR
    A["/ (الرئيسية)"] --> B["/pricing (التسعير)"]
    A --> C["/about (عن)"]
    A --> D["/contact (اتصل)"]
    A --> E["/dashboard (لوحة التحكم)"]
    E --> F["/dashboard/shops"]
    E --> G["/dashboard/analytics"]

(2) تعريفات المسارات

PHP
// 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) ▶ مثال: إنشاء جميع المتحكمات

BASH
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

الناتج:

TEXT
# تم تنفيذ الأمر بنجاح

4. تصميم التخطيط والمكونات

(1) هيكل التخطيط

TEXT
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

BASH
# إنشاء ملفات المكونات يدويًا
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

الناتج:

TEXT
# تم تنفيذ الأمر بنجاح

5. تنفيذ التخطيط والمكونات الأساسية

(1) التخطيط الرئيسي

HTML
<!-- 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) مكون شريط التنقل

HTML
<!-- 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

HTML
<!-- 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) ▶ مثال: مكون بطاقة التسعير

HTML
<!-- 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>

الناتج:

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

6. تنفيذ المتحكم

(1) ▶ مثال: HomeController وPricingController

PHP
// 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'));
    }
}

الناتج:

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

(2) ▶ مثال: DashboardController

PHP
// 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'));
    }
}

الناتج:

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

7. تهيئة البيئة وقاعدة البيانات

BASH
# .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 الرئيسية

HTML
<!-- ============================================
     شامل: صفحة هبوط 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

❓ أسئلة شائعة

س هل تتطلب صفحات الهبوط مصادقة؟
ج الصفحة الرئيسية وصفحة التسعير وصفحة عن لا تتطلب مصادقة ويمكن لأي شخص الوصول إليها؛ لوحة التحكم محمية ببرمجية auth الوسيطة. Breeze تعالج تلقائيًا مسارات تسجيل الدخول/الإنشاء.
س كيف أختار بين وراثة التخطيط والمكونات؟
ج استخدم وراثة التخطيط لهياكل مستوى الصفحة (مثل شريط تنقل وتذييل يحيطان بالمحتوى)؛ استخدم مكونات Blade لمقاطع UI القابلة لإعادة الاستخدام (مثل البطاقات والأزرار والنماذج). التخطيطات تحدد "الهيكل"، والمكونات تملأ "اللحم".
س ماذا أفعل إذا لم يعمل Tailwind CSS؟
ج تأكد من تشغيل npm run dev (إعادة تحميل ساخنة في وضع التطوير) أو npm run build (بناء الإنتاج)، وتحقق من أن تهيئة content في tailwind.config.js تتضمن مسارات جميع ملفات Blade.
س هل x-show في Alpine.js هو نفسه v-show في Vue؟
ج يخدمان غرضًا مشابهًا، لكن Alpine.js مكتبة مستقلة خفيفة (15 كيلوبايت) لا تتطلب عملية بناء؛ v-show في Vue يتطلب نسخة Vue. استخدم Alpine.js للتفاعلات البسيطة وVue لتطبيقات الصفحة الواحدة المعقدة.
س ما الفرق بين Breeze وJetstream؟
ج Breeze هو هيكل مصادقة خفيف (اختر واحدًا من Blade أو Inertia أو Livewire)؛ كوده أقل وسهل الفهم. Jetstream حل أكثر ثراءً (يتضمن إدارة الفرق والمصادقة الثنائية)، لكنه أكثر تعقيدًا. نوصي بالبدء بـ Breeze.
س كيف أتعامل مع البيانات المشتركة بين متحكمات متعددة؟
ج استخدم View Composer لضخ البيانات المشتركة تلقائيًا (مثل قائمة المتاجر في شريط التنقل)، أو أنشئ صنف Service لمشاركة المنطق عبر المتحكمات. لا تشارك البيانات في صنف المتحكم الأساسي.

📖 ملخص


📝 تمارين

  1. مهمة أساسية (⭐): أكمل الصفحات الخمس لصفحة هبوط ShopMetrics (الرئيسية/التسعير/عن/اتصل/لوحة التحكم)، مع التأكد من إمكانية الوصول إلى جميع المسارات، ووراثة التخطيط تعمل بشكل صحيح، وروابط شريط التنقل صحيحة.

  2. تمرين متقدم (⭐⭐): أضف تبديل دفع شهري/سنوي إلى صفحة التسعير (باستخدام Alpine.js). يجب أن يكون السعر السنوي بخصم 20%، وسعر البطاقة ينتقل برسوم متحركة عند التبديل بين خيارات الدفع.

  3. تحدٍ (⭐⭐⭐): نفذ قائمة ديناميكية في شريط لوحة التحكم الجانبي — اعرض عناصر قائمة مختلفة بناءً على دور المستخدم (admin/tenant_owner/analyst)، باستخدام View Composer لاسترداد تهيئات الصلاحيات من قاعدة البيانات.

Web-Tutorial.com

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

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

100%