متن خبر

نحوه استفاده از React.js با لاراول برای ساختن یک برنامه Tasklist قابل کشیدن

نحوه استفاده از React.js با لاراول برای ساختن یک برنامه Tasklist قابل کشیدن

شناسهٔ خبر: 445725 -




ممکن است آموزش‌هایی را دیده باشید که به شما کمک می‌کنند تا یک برنامه ساده React.js بسازید که از برخی API شخص ثالث یا سرور Node.js به عنوان پشتیبان استفاده می‌کند. همچنین می توانید از لاراول برای این منظور استفاده کنید و آن را با React ادغام کنید.

لاراول به عنوان یک فریم ورک باطن، در واقع ابزاری را به شما ارائه می دهد که اینرسی نام دارد. در اینجا آنچه اسناد در مورد آن می گویند:

این شکاف بین برنامه Laravel شما و Vue یا React frontend مدرن شما را پر می کند، و به شما این امکان را می دهد که با استفاده از Vue یا React، در حالی که از مسیرها و کنترلرهای Laravel برای مسیریابی، هیدراتاسیون داده ها و احراز هویت استفاده می کنید، فرانت اندهای کامل و مدرن بسازید. مخزن

اما اگر نخواهید از چنین ابزاری استفاده کنید چه؟ و در عوض، شما فقط می خواهید از React.js به عنوان یک کتابخانه frontend استفاده کنید و یک باطن ساده با قدرت لاراول داشته باشید؟

خوب، در این مقاله، نحوه استفاده از React.js را با لاراول به عنوان یک Backend با ساخت یک برنامه tasklist قابل کشیدن یاد خواهید گرفت.

برای این برنامه تک صفحه ای تمام پشته، از Vite.js به عنوان ابزار ساخت ظاهری خود و بسته react-beautiful-dnd برای موارد قابل کشیدن استفاده خواهید کرد.

در پایان این مقاله، یک اپلیکیشن تک صفحه ای برای مدیریت وظایف خواهید داشت که به شکل زیر خواهد بود:

فهرست وظیفه یا فهرست  کار
از یک پروژه کاری محلی گرفته شده است

در این مقاله، یک صفحه پویا ایجاد می کنیم که دارای فهرست ی از وظایف است که هر کدام به پروژه خاصی تعلق دارند. به این ترتیب کاربر قادر به انتخاب پروژه خواهد بود و تنها وظایف پروژه انتخاب شده در صفحه نمایش داده می شود. کاربر همچنین می تواند یک کار جدید برای پروژه فعلی ایجاد کند و همچنین با کشیدن و رها کردن کارها را ویرایش، حذف و مرتب کند.

فهرست مطالب:

پیش نیازها

Backend: چگونه لاراول را نصب کنیم

نحوه ایجاد مدل ها و مهاجرت ها

نحوه ایجاد بذرپاشی

نحوه اتصال به پایگاه داده MySQL

تزریق خدمات

مسیرهای وب و API در لاراول

درخواست های اعتبارسنجی در لاراول

چگونه یک کنترلر بنویسیم که از خدمات استفاده می کند

نحوه تست مسیرهای API

The Frontend: نحوه نصب بسته ها

نحوه پیکربندی Vite.js

React.js – ادغام اولیه

نحوه اضافه کردن CSS

یک سرویس برای درخواست های API

اجزای React.js

نتایج نهایی

نتیجه

پیش نیازها

قبل از ادامه، داشتن یک درک اولیه از React.js، Laravel و آشنایی با مفاهیم اساسی توسعه وب مفید خواهد بود.

برای برنامه ای که در این مقاله می سازیم به ابزارهای زیر نیاز دارید:

PHP 8.1 یا بالاتر (برای تحلیل نسخه php -v اجرا کنید)

آهنگساز ( composer اجرا کنید تا تحلیل کنید که وجود دارد)

Node.js 18 یا بالاتر (برای تحلیل نسخه node -v اجرا کنید)

MySQL 5.7 یا بالاتر ( mysql --version برای تحلیل وجود آن اجرا کنید یا اسناد را دنبال کنید)

ابزارهای اضافی (اختیاری) که می توانید استفاده کنید:

Postman – برنامه ای با رابط کاربری برای آزمایش مسیرهای API

curl - یک دستور CLI برای آزمایش مسیرهای API

ما با ساختن بک‌اند شروع می‌کنیم و سپس به سمت جلو حرکت می‌کنیم.

Backend: چگونه لاراول را نصب کنیم

ابتدا، اگر قبلاً آن را ندارید، باید فریم ورک لاراول را روی دستگاه محلی خود نصب کنید.

یکی از راه های نصب لاراول استفاده از یک Dependency Manager محبوب برای PHP به نام Composer است. در اینجا دستور انجام این کار وجود دارد:

 composer create-project laravel/laravel tasklist

با این کار آخرین نسخه پایدار لاراول در دستگاه محلی شما نصب می شود (در حال حاضر نسخه 10 است).

لیست وظایف موجود در دستور، نام پوشه اصلی برنامه است که می توانید آن را روی هر چیزی که می خواهید تنظیم کنید.

در این مرحله، می‌توانید در پوشه پروژه cd و برنامه Backend را بدون نیاز به راه‌اندازی سرور مجازی اجرا کنید:

 cd tasklist/ && php artisan serve

artisan در دستور بالا یک ابزار CLI است که در لاراول گنجانده شده است. این فایل در ریشه برنامه لاراول شما به عنوان فایل اسکریپت artisan وجود دارد که تعدادی دستورات مفید را ارائه می دهد که می تواند در هنگام ساخت برنامه به شما کمک کند.
ما در این مقاله اغلب از آن استفاده خواهیم کرد.

برای مشاهده صفحه پیش فرض، در مرورگر خود به http://127.0.0.1:8000 مراجعه کنید. می بایست شبیه به این باشه:

لاراول
صفحه خوش آمدگویی لاراول

نحوه ایجاد مدل ها و مهاجرت ها

حال، بیایید مدل‌های Project و Task و همچنین مهاجرت‌ها را برای آنها ایجاد کنیم.

مدل‌ها روشی هستند که نهادهای برنامه شما باید تعریف شوند، و مهاجرت‌ها مانند تعاریف طرحواره برای ذخیره رکوردهای آن موجودیت‌ها در پایگاه داده هستند.

شما می توانید فایل های مدل و مهاجرت را به صورت دستی ایجاد کنید و همچنین با استفاده از دستور artisan آنها را تولید کنید:

 php artisan make:model Project -m php artisan make:model Task -m

آرگومان -m به طور خودکار یک فایل مهاجرت را با استفاده از نام مدل ارائه شده ایجاد می کند.

دنباله اجرای دستور را همانطور که هست نگه دارید، تا انتقال پروژه بعداً قبل از انتقال Task اجرا شود.

این مهم است، زیرا جداول projects و tasks باید یک رابطه یک به چند داشته باشند (1-N): هر وظیفه به یک پروژه منفرد اشاره دارد، یا به عبارت دیگر، هر پروژه می تواند چندین کار داشته باشد.

فیلدهای $fillable مدل Project و روش رابطه task() را به صورت زیر تنظیم کنید:

 <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Project extends Model { use HasFactory; protected $table = 'projects'; public $timestamps = false; protected $fillable = [ 'id', // primary key, auto-increment, integer 'name', // string ]; // a project can have multiple tasks public function tasks(): HasMany { return $this->hasMany(Task::class); } }
app/Models/Project.php

به طور پیش فرض، ویژگی عمومی $timestamps دارای یک مقدار true است که از کلاس Model والد می آید. این بدان معنی است که ستون های created_at و updated_at در جدول پایگاه داده شما به طور خودکار توسط Eloquent (ORM موجود در لاراول) نگهداری می شوند.

اما می توانید با تغییر مقدار آن به false آن را سفارشی کنید. نیازی نیست که فیلدهای created_at و updated_at را در جدول projects داشته باشیم، پس $timestamps را روی false قرار می دهیم.

فیلدهای $fillable مدل Task ، روش رابطه project() و Accessor created تنظیم کنید. یک Accessor در لاراول مانند یک تابع بین پایگاه داده و کد شما است که می تواند به رکورد پایگاه داده از قبل واکشی شده دسترسی پیدا کند و آن را تغییر دهد.

 <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; class Task extends Model { use HasFactory, SoftDeletes; protected $table = 'tasks'; protected $fillable = [ 'id', // primary key, auto-increment, integer 'project_id', // foreign key, integer 'priority', // integer 'title', // string 'description', // text ]; protected $appends = [ 'created', ]; // each task belongs to a single project public function project(): BelongsTo { return $this->belongsTo(Project::class); } public function getCreatedAttribute() { return $this->created_at->diffForHumans(); } }
app/Models/Task.php

در بالا، در مدل Task ، یک ابزار دسترسی به نام created وجود دارد. برای داشتن یک Accessor، فیلد created در آرایه $appends و همچنین یک تابع عمومی getCreatedAttribute() داریم.

در تابع get <WordsInCamelCase> Attribute() منطقی وجود دارد که برای تغییر رکورد پایگاه داده از قبل واکشی شده اجرا می شود.

در مورد ما تابع getCreatedAttribute() یک تفاوت زمانی مناسب و قابل خواندن بین زمان فعلی و زمان داده شده را برمی گرداند. به عنوان مثال، "3 دقیقه پیش" یا "4 ماه پیش" .

اکنون که مدل ها آماده هستند، بیایید مهاجرت ها را تنظیم کنیم.

ابتدا یک مهاجرت برای جدول projects تنظیم کنید:

 <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('projects', function (Blueprint $table) { $table->id(); $table->string('name'); }); } public function down(): void { Schema::dropIfExists('projects'); } };
database/migrations/2024_01_14_171103_create_projects_table.php

سپس یک مهاجرت برای جدول tasks تنظیم کنید:

 <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->foreignId('project_id')->nullable()->constrained(); $table->integer('priority'); $table->string('title'); $table->text('description')->nullable(); $table->timestamps(); $table->softDeletes(); }); } public function down(): void { // drop existing foreign keys Schema::table('tasks', function (Blueprint $table) { if (Schema::hasColumn('tasks', 'project_id')) { $table->dropForeign(['project_id']); } }); // drop the table Schema::dropIfExists('tasks'); } };
database/migrations/2024_01_14_171114_create_tasks_table.php

جدول tasks دارای یک کلید خارجی project_id است که اشاره ای به جدول projects است. پس تمرین خوبی است که متد down() را نیز به روز کنید تا مطمئن شوید که project_id خارجی قبل از انداختن جدول projects واقعی حذف می شود.

یک فیلد priority نیز وجود دارد که یک عدد طبیعی غیر قابل تهی برای سفارش دادن وظایف خواهد بود. و به صورت اختیاری، می توانید یک ویژگی حذف نرم به مدل Task اضافه کنید.

نحوه ایجاد بذرپاشی

اکنون باید داده های ساختگی را به جداول projects و tasks اضافه کنیم. برای کاشت برخی از داده ها در پایگاه داده، می توانید از Laravel seeders استفاده کنید. این به شما اجازه می دهد تا داده های ساختگی را برای استفاده در پایگاه داده خود ایجاد کنید.

اگر می‌خواهید درباره نحوه عملکرد این روش بیشتر بخوانید، می‌توانید اسناد را در اینجا تحلیل کنید .

لاراول با استفاده از دستور make:seeder artisan راهی برای تولید آن فایل ها ارائه می دهد:

 php artisan make:seeder ProjectsSeeder php artisan make:seeder TasksSeeder

پس با دستورات بالا، فایل های database/seeders/ProjectsSeeder.php و database/seeders/TasksSeeder.php را خواهید داشت.

در ابتدا باید ProjectsSeeder راه اندازی کنید تا چند پروژه را به جدول projects اضافه کنید. سپس می توانید TasksSeeder را برای اضافه کردن وظایف به جدول tasks تنظیم کنید.

همانطور که در ابتدا اشاره کردم، هر کار متعلق به یک پروژه خاص خواهد بود. از منظر پایگاه داده رابطه ای، این بدان معنی است که هر ورودی در جدول tasks به یک ورودی خاص در جدول projects پیوند می خورد. در اینجا اهمیت وجود یک کلید خارجی project_id در جدول tasks وجود دارد تا بتوانید هر کار را به یک پروژه خاص مرتبط کنید و همچنین وظایف پروژه خاص را بازیابی کنید.

با مشاهده تصاویر زیر می توانید ساختار پایگاه داده را تصور کنید:

laravel_react_tasklist-1
تولید شده توسط PHPStorm IDE

با استفاده از مثال زیر می توانید 3 پروژه ایجاد کنید:

 <?php namespace Database\Seeders; use App\Models\Project; use Illuminate\Database\Seeder; class ProjectsSeeder extends Seeder { public function run(): void { for ($i = 1; $i <= 3; $i++) { Project::create([ 'name' => "Project $i", ]); } } }
database/seeders/ProjectsSeeder.php

در مرحله بعد، TasksSeeder راه اندازی کنید. پس از راه‌اندازی، تمام فایل‌های Seder را اجرا می‌کنید و آنها یکی یکی اجرا می‌شوند. همانطور که گفته شد، در این مرحله ProjectsSeeder شما آماده ایجاد چند پروژه است.

با تصور آن، مرحله بعدی تولید وظایف خواهد بود، هر یک از آنها به یکی از پروژه های موجود توسط فیلد project_id خود ارجاع خواهند داد.

با استفاده از مثال زیر می توانید 10 پروژه ایجاد کنید:

 <?php namespace Database\Seeders; use App\Models\Project; use App\Models\Task; use Illuminate\Database\Seeder; class TasksSeeder extends Seeder { public function run(): void { $project_ids = Project::all()->pluck('id')->toArray(); $now = now(); $tasks = []; $project_priorities = []; foreach ($project_ids as $project_id) { $project_priorities[$project_id] = 0; } for ($i = 1; $i <= 10; $i++) { $project_id = $project_ids[array_rand($project_ids)]; $project_priorities[$project_id]++; $tasks[] = [ 'project_id' => $project_id, 'priority' => $project_priorities[$project_id], 'title' => "Task " . $project_priorities[$project_id], 'description' => "Description for Task " . $project_priorities[$project_id], 'created_at' => $now, 'updated_at' => $now, ]; } Task::insert($tasks); } }
پایگاه داده/seeders/TasksSeeder.php

کد بالا فقط تمام شناسه های پروژه را می گیرد، سپس به طور تصادفی یک پروژه را برای هر کار انتخاب می کند. در پایان، تمام وظایف را در جدول tasks قرار می دهد.

همانطور که متوجه شده اید، ما $tasks با استفاده از تابع static insert() در جدول tasks وارد می کنیم، که به ما امکان می دهد تمام موارد را با یک پرس و جو در جدول پایگاه داده قرار دهیم.

اما یک نقطه ضعف نیز دارد: فیلدهای created_at و updated_at را مدیریت نمی کند. به همین دلیل است که باید آن فیلدها را به صورت دستی با تخصیص همان مهر زمانی $now به آنها تنظیم کنید.

اکنون، هنگامی که تمام seder ها را آماده کردید، باید آنها را در DatabaseSeeder ثبت کنید.

 <?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run(): void { $this->call([ ProjectsSeeder::class, TasksSeeder::class, ]); } }
database/seeders/DatabaseSeeder.php

نحوه اتصال به پایگاه داده MySQL

قبل از اجرای migrations و seeds، یک پایگاه داده MySQL ایجاد کنید و اعتبار مناسب را در فایل .env تنظیم کنید. اگر .env وجود ندارد، آن را ایجاد کرده و محتوای فایل .env.example را در آن قرار دهید.

پس از تنظیم اعتبار پایگاه داده، این نوع متغیرهای محیطی را خواهید داشت:

 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE="<DATABASE_NAME>" DB_USERNAME="<USERNAME>" DB_PASSWORD="<PASSWORD>"
env

پس از تنظیم متغیرهای محیط، کش را بهینه کنید:

 php artisan optimize

اکنون می توانید جداول projects و tasks را در پایگاه داده MySQL ایجاد کنید، ساختار آنها را تنظیم کنید و رکوردهای اولیه را با یک دستور اضافه کنید:

 php artisan migrate:fresh --seed

در دستور بالا، آرگومان migrate:fresh همه جداول را از پایگاه داده حذف می کند. سپس دستور migrate را اجرا می کند که مهاجرت های شما را برای ایجاد projects و جداول tasks به درستی اجرا می کند.

با آرگومان --seed ، ProjectsSeeder و TasksSeeder را پس از مهاجرت اجرا می کند. همانطور که گفته شد، پایگاه داده شما را برای شما خالی می کند و تمام جداول را ایجاد می کند و تمام داده های ساختگی لازم را پر می کند.

پس از اجرای دستور، این نوع رکوردهای پایگاه داده را خواهید داشت:

تصویر-74
تصویری از PHPStorm IDE

تزریق خدمات

حالا بیایید یک کنترلر و یک کلاس سرویس ایجاد کنیم تا تمام ویژگی های وظیفه را مدیریت کنیم، مانند فهرست کردن، ایجاد، به روز رسانی، حذف و مرتب کردن مجدد وظایف.

ابتدا از دستور زیر برای تولید یک کنترلر استفاده کنید.

 php artisan make:controller TaskController

برای اینکه همه کدها در کنترلر قرار نگیرند، می توانید فقط منطق اصلی را در آن نگه دارید و سایر پیاده سازی های منطقی را به یک فایل کلاس دیگر منتقل کنید.

این کلاس‌ها عموماً خدمات نامیده می‌شوند و استفاده از پیاده‌سازی سرویس در روش کنترل‌کننده، تزریق سرویس نامیده می‌شود (از اصطلاح تزریق وابستگی می‌آید).

یکی از مزایای اصلی استفاده از سرویس ها این است که به شما کمک می کند یک پایگاه کد قابل نگهداری ایجاد کنید.

می توانید کلاس سرویس خود را به عنوان آرگومان به متد ساخت کنترلر تزریق کنید، پس پس از اجرای هر کنترلر (زمانی که متد __construct() کنترلر اجرا می شود) می توانید یک شی از سرویس را مقداردهی اولیه کنید. این بدان معنی است که شما می توانید به عملکردهای سرویس خود مستقیماً در کنترلر خود دسترسی داشته باشید.

اکنون، بیایید دو کلاس سرویس جداگانه ایجاد کنیم که در TaskController استفاده می شود.

به صورت دستی یک کلاس سرویس app/Services/ProjectService.php ایجاد کنید که مسئولیت منطق مربوط به پروژه را بر عهده خواهد داشت.

 <?php namespace App\Services; use App\Models\Project; use Illuminate\Database\Eloquent\Collection; class ProjectService { public function getAll(): Collection { return Project::all(); } }
app/Services/ProjectService.php

کلاس سرویس دوم app/Services/TaskService.php خواهد بود که مسئول انجام دستکاری وظایف خواهد بود:

 <?php namespace App\Services; use App\Models\Task; use Illuminate\Support\Facades\DB; class TaskService { public function list(int $projectId) { return Task::with('project')->where('project_id', $projectId) ->orderBy('priority')->get(); } public function getById(int $id) { return Task::where('id', $id)->with('project')->first(); } public function store($data): void { $count = Task::where('project_id', $data['project_id'])->count(); $data['priority'] = $count + 1; Task::create($data); } public function update(int $id, array $data): void { $task = $this->getById($id); if (!$task) { return; } $task->update($data); } public function delete(int $id): void { $task = $this->getById($id); if (!$task) { return; } $task->delete(); $tasks = Task::where('project_id', $task->project_id) ->where('priority', '>', $task->priority)->get(); if ($tasks->isEmpty()) { return; } $when_then = ""; $where_in = ""; foreach ($tasks as $task) { $when_then .= "WHEN ".$task->id ." THEN ".($task->priority - 1)." "; $where_in .= $task->id.","; } $table_name = (new Task())->getTable(); $bulk_update_query = "UPDATE `".$table_name ."` SET `priority` = (CASE `id` ".$when_then."END)" ." WHERE `id` IN(".substr($where_in, 0, -1).");"; // there is no way to be SQL injected here // because all the values are not provided by the user DB::update($bulk_update_query); } public function reorder(int $project_id, int $start, int $end): void { $items = Task::where('project_id', $project_id) ->orderBy('priority')->pluck('priority', 'id')->toArray(); if ($start > count($items) || $end > count($items)) { return; } $ids = []; $priorities = []; foreach ($items as $id => $priority) { $ids[] = $id; $priorities[] = $priority; } $out_priority = array_splice($priorities, $start - 1, 1); array_splice($priorities, $end - 1, 0, $out_priority); $when_then = ""; $where_in = ""; foreach ($priorities as $out_k => $out_v) { $id = $ids[$out_v - 1]; $when_then .= "WHEN ".$id." THEN ".($out_k + 1)." "; $where_in .= $id.","; } $table_name = (new Task())->getTable(); $bulk_update_query = "UPDATE `".$table_name ."` SET `priority` = (CASE `id` ".$when_then."END)" ." WHERE `id` IN(".substr($where_in, 0, -1).")" ." AND `deleted_at` IS NULL;"; // soft delete DB::update($bulk_update_query); } }
app/Services/TaskService.php

در کلاس TaskService فوق، از توابع زیر در TaskController استفاده خواهید کرد.

لیست : وظایف را برای شناسه پروژه معین، از جمله پروژه مرتبط، واکشی می کند و آنها را بر اساس اولویت مرتب می کند.

getById : یک کار خاص را با شناسه خود بازیابی می کند، از جمله پروژه مرتبط.

store : یک کار جدید را ذخیره می کند و اولویت را بر اساس وظایف موجود برای همان پروژه محاسبه می کند.

به روز رسانی : یک کار موجود را با شناسه آن به روز می کند.

حذف : یک کار را با شناسه خود حذف می کند و اولویت های کارهای باقی مانده در همان پروژه را تنظیم می کند.

reorder : اولویت‌های وظایف را در یک پروژه تغییر می‌دهد (همچنین با deleted_at IS NULL حذف نرم را کنترل می‌کند).

مسیرهای وب و API در لاراول

اکنون می توانید مسیرهایی را برای آزمایش روش هایی که قبلاً نوشته اید اضافه کنید. در این پروژه، ما یک برنامه بدون حالت در فرانت اند داریم که مسیرهای API را برای دریافت داده های JSON درخواست می کند، پس از اصول RESTful (روش های GET، POST، PUT، DELETE) پیروی می کند. فقط صفحه HTML اولیه به عنوان یک صفحه وب کامل بازیابی می شود.

پس اکنون، مسیری را در routes/web.php برای تک صفحه اولیه تنظیم کنید:

 <?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\TaskController; Route::group(['prefix' => '/', 'as' => 'tasks.'], function () { Route::get('/', [TaskController::class, 'index'])->name('index'); });
routes/web.php

مسیرهای API را در routes/api.php به این صورت تنظیم کنید:

 <?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\TaskController; Route::group(['prefix' => '/tasks', 'as' => 'tasks.'], function () { Route::get('/', [TaskController::class, 'list']); Route::get('/{id}', [TaskController::class, 'get']) ->where('id', '[1-9][0-9]*'); Route::post('/', [TaskController::class, 'store']); Route::put('/{id}', [TaskController::class, 'update']) ->where('id', '[1-9][0-9]*'); Route::delete('/{id}', [TaskController::class, 'delete']) ->where('id', '[1-9][0-9]*'); Route::put('/', [TaskController::class, 'reorder']); });
routes/api.php

ما همه مسیرهای API را به جای routes/api.php در routes/web.php داریم زیرا در فایل web.php ، همه مسیرها به طور پیش‌فرض دارای CSRF محافظت می‌شوند. پس ، در یک برنامه بدون حالت، معمولاً به آن نیاز نخواهید داشت – به همین دلیل api.php در لاراول اختراع شد.

همانطور که می بینید، یک پیشوند "وظیفه" برای همه مسیرهای API وجود دارد. داشتن یک پیشوند اختیاری است، اما این فقط یک تمرین خوب است. و برای مسیرهای API خاص، اعتبار سنجی regex برای پذیرش فقط اعداد طبیعی به عنوان شناسه پروژه وجود دارد.

فراموش نکنید که حافظه پنهان مسیر را پس از تغییرات بالا به روز کنید. مهم است که به خاطر داشته باشید که لاراول (در این مورد نسخه 10) مسیرها را از فایل cache bootstrap/cache/routes-v7.php می خواند و بلافاصله پس از تغییرات شما به طور خودکار به روز نمی شوند. در صورتی که هنوز کش نشده باشد، یکی را ایجاد می کند.

از دستور زیر برای رفرش کش لاراول و همچنین کش مسیر استفاده کنید:

 php artisan optimize

درخواست های اعتبارسنجی در لاراول

قبل از نوشتن روش های کنترلر، باید چند فایل درخواست اعتبار سنجی اضافه کنید. می توانید این کار را به صورت دستی یا فقط با استفاده از دستور artisan انجام دهید:

 php artisan make:request Task/CreateTaskRequest php artisan make:request Task/ListTasksRequest php artisan make:request Task/ReorderTasksRequest php artisan make:request Task/UpdateTaskRequest

پس از ایجاد آنها، باید قوانین اعتبارسنجی را برای هر درخواست تنظیم کنید.

قوانین اعتبارسنجی در لاراول روشی برای توصیف چگونگی انتظار دریافت داده های HTTP ورودی است. اگر داده ها با قوانین مطابقت داشته باشند، اعتبارسنجی را پشت سر می گذارند - در غیر این صورت، لاراول یک شکست را برمی گرداند.

لاراول مجموعه ای از قوانین را ارائه می دهد که می توانید از آنها برای تحلیل داده های دریافتی استفاده کنید. یک فیلد از درخواست ورودی می تواند چندین قانون داشته باشد.

یکی از راه‌های نوشتن قوانین اعتبارسنجی برای یک فیلد، به هم پیوستن آن قوانین توسط یک "|" است. شخصیت.

در زیر قوانین اعتبارسنجی برای ایجاد یک کار جدید آمده است:

 return [ 'project_id' => 'required|integer|exists:projects,id', 'title' => 'required|string|max:255', 'description' => 'nullable|string', ];
app/Http/Requests/Task/CreateTaskRequest.php

در زیر قانون اعتبارسنجی برای فهرست کردن وظایف پروژه آمده است:

 return [ 'project_id' => 'required|integer|exists:projects,id', ];
app/Http/Requests/Task/ListTasksRequest.php

در زیر قوانین اعتبارسنجی برای ترتیب مجدد کارها آمده است:

 return [ 'project_id' => 'required|integer|exists:projects,id', 'start' => 'required|integer', 'end' => 'required|integer|different:start', ];
app/Http/Requests/Task/ReorderTasksRequest.php

در زیر قوانین اعتبارسنجی برای به‌روزرسانی یک کار وجود دارد:

 return [ 'title' => 'required|string|max:255', 'description' => 'nullable|string', ];
app/Http/Requests/Task/UpdateTaskRequest.php

فراموش نکنید که true در متد authorize() در تمام کلاس های اعتبارسنجی برگردانید:

 public function authorize(): bool { return true; }
برای تمام کلاس های اعتبار سنجی سفارشی

این تابع معمولاً برای تعیین اینکه آیا کاربر مجاز به درخواست است یا خیر طراحی شده است. از آنجایی که ما از احراز هویت و همچنین موارد مجوز در برنامه استفاده نمی‌کنیم، باید برای همه موارد true باشد.

چگونه یک کنترلر بنویسیم که از خدمات استفاده می کند

به عنوان آخرین مرحله در بخش Backend، زمان نوشتن متدهای کنترل کننده برای هر مسیر API است که از توابع سرویس استفاده می کند.

 <?php namespace App\Http\Controllers; use App\Http\Requests\Task\CreateTaskRequest; use App\Http\Requests\Task\ListTasksRequest; use App\Http\Requests\Task\ReorderTasksRequest; use App\Http\Requests\Task\UpdateTaskRequest; use App\Services\ProjectService; use App\Services\TaskService; use Illuminate\Http\JsonResponse; class TaskController extends Controller { protected ?TaskService $taskService = null; public function __construct(TaskService $taskService) { $this->taskService = $taskService; } public function index() { $projects = (new ProjectService())->getAll(); return view('tasks.index', [ 'projects' => $projects, ]); } public function list(ListTasksRequest $request): JsonResponse { $tasks = $this->taskService->list($request->get('project_id')); return response()->json([ 'success' => true, 'tasks' => $tasks, 'message' => "Tasks retrieved successfully.", ]); // 200 } public function store(CreateTaskRequest $request): JsonResponse { $this->taskService->store($request->all()); return response()->json([ 'success' => true, 'message' => "Task created successfully.", ], 201); } public function get(int $id): JsonResponse { $task = $this->taskService->getById($id); if ($task) { return response()->json([ 'success' => true, 'task' => $task, 'message' => "Task retrieved successfully.", ]); // 200 } else { return response()->json([ 'success' => false, 'message' => "Task not found!", ], 404); } } public function update(UpdateTaskRequest $request, int $id): JsonResponse { $this->taskService->update($id, $request->all()); return response()->json([ 'success' => true, 'message' => "Task updated successfully.", ], 201); } public function delete(int $id): JsonResponse { $this->taskService->delete($id); return response()->json([ 'success' => true, 'message' => "Task deleted successfully.", ], 201); } public function reorder(ReorderTasksRequest $request): JsonResponse { $this->taskService->reorder( $request->get('project_id'), $request->get('start'), $request->get('end') ); return response()->json([ 'success' => true, 'message' => "Tasks reordered successfully.", ], 201); } }
app/Http/Controllers/TaskController.php

همانطور که در TaskController:

TaskService به عنوان آرگومان به متد سازنده تزریق می شود. در بدنه سازنده، نمونه ای از کلاس TaskService ایجاد می شود و ویژگی $taskService مقداردهی اولیه می شود. پس در روش های سفارشی، می توانید به آن $taskService و عملکردهای آن دسترسی داشته باشید.

روش index برای برگرداندن HTML است.

همه روش‌های سفارشی دیگر ( list ، store ، get ، update ، delete ، reorder ) از توابع TaskService از طریق ویژگی $taskService مقداردهی اولیه شده استفاده می‌کنند. پس ، تمام پیاده سازی منطق به سرویس می رود، و به این ترتیب، شما فقط یک تابع سرویس را فراخوانی می کنید و پاسخ را برمی گردانید.

نحوه تست مسیرهای API

در این مرحله، می‌توانید مسیرهای API را با درخواست از طریق Postman یا هر ابزار مشابهی آزمایش کنید. فقط باطن را اجرا کنید (یا دوباره اجرا کنید):

 php artisan serve

در اینجا مجموعه منتشر شده پستچی با تمام درخواست ها است.

به جای استفاده از Postman، می توانید از یک ابزار خط فرمان مانند curl از ترمینال خود استفاده کنید.

در زیر تمام دستورات نمونه ای وجود دارد که می توانید برای آزمایش مسیرهای API اجرا کنید:

ایجاد یک کار جدید برای یک پروژه خاص:

 curl --location '127.0.0.1:8000/api/tasks?project_id=1' \ --header 'Content-Type: application/json' \ --data '{ "title": "Title", "description": "Description" }'

فهرست وظایف پروژه:

 curl --location 'http://127.0.0.1:8000/api/tasks?project_id=1'

دریافت یک کار با شناسه:

 curl --location 'http://127.0.0.1:8000/api/tasks/1'

به روز رسانی یک کار با شناسه:

 curl --location --request PUT 'http://127.0.0.1:8000/api/tasks/11' \ --header 'Content-Type: application/json' \ --data '{ "title": "Title edited", "description": "Description edited" }'

سفارش مجدد وظایف پروژه:

 curl --location --request PUT 'http://127.0.0.1:8000/api/tasks' \ --header 'Content-Type: application/json' \ --data '{ "project_id": "1", "start": "1", "end": "2" }'

حذف یک کار با شناسه:

 curl --location --request DELETE 'http://127.0.0.1:8000/api/tasks/11'

در تصویر زیر، نمونه ای از دریافت وظایف پروژه با شناسه آن با استفاده از دستور curl (در پایین سمت راست) وجود دارد:

تصویر-84
مثالی از استفاده از دستور curl برای بازیابی وظایف موجود از پایگاه داده

The Frontend: نحوه نصب بسته ها

اکنون زمان آن است که به قسمت ظاهری تغییر دهید. ما از TypeScript برای React.js استفاده خواهیم کرد. پس از تکمیل این بخش، می‌توانید React.js (با Vite) را در برنامه لاراول خود ادغام کنید.

ابتدا با استفاده از این دستور مطمئن شوید که Node.js نسخه 18 یا بالاتر را دارید:

 node -v

این بسته های npm ضروری را نصب کنید:

 npm i react-dom dotenv react-beautiful-dnd react-responsive-modal react-toastify @vitejs/plugin-react

react-dom یک کتابخانه از تیم React برای رندر کردن اجزای React در DOM (مدل شیء سند) است.

dotenv برای بارگذاری متغیرهای محیطی از فایل .env در محیط فرآیند است

react-beautiful-dnd یک کتابخانه از Atlassian برای ایجاد رابط های کشیدن و رها کردن با انیمیشن ها است.

react-responsive-modal برای ایجاد دیالوگ های مدال ساده و پاسخگو است

react-toastify برای نمایش اعلان ها یا نان تست ها است

@vitejs/plugin-react یک پلاگین برای ابزار ساخت Vite است که امکان ادغام یکپارچه React را با توسعه سریع و ساخت‌های تولید بهینه می‌کند.

با این دستور وابستگی های توسعه را نصب کنید:

 npm i -D @types/react-dom @types/react-beautiful-dnd

@types/react-dom تعاریف نوع TypeScript برای بسته react-dom است

@types/react-beautiful-dnd تعاریف نوع TypeScript برای بسته react-beautiful-dnd است.

نحوه پیکربندی Vite.js

از آنجایی که Laravel V10 قبلاً vite.config.js دارد، می‌خواهید هر چیز مرتبط با React را در آنجا تنظیم کنید. یا اگر هنوز این فایل را ندارید، یکی مانند این ایجاد کنید:

 import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import react from '@vitejs/plugin-react'; import 'dotenv/config'; export default defineConfig({ build: { minify: process.env.APP_ENV === 'production' ? 'esbuild' : false, cssMinify: process.env.APP_ENV === 'production', }, plugins: [ laravel({ input: ['resources/react/app.tsx'], refresh: true, }), react(), ], });
vite.config.js

همانطور که در فایل پیکربندی Vite می بینید، مرجعی به resources/react/app.tsx وجود دارد که نقطه ورود لاراول برای استفاده از منابع React خواهد بود.

برای صفحه اولیه HTML، یک فایل resources/views/tasks/index.blade.php blade ایجاد کنید، پس تمام دارایی های frontend در app div با ID در آنجا تزریق می شوند:

 <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ config("app.name") }}</title> <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> @viteReactRefresh @vite('resources/react/app.tsx') </head> <body> <div id="app" data-projects="{{ json_encode($projects) }}"></div> </body> </html>
resources/views/tasks/index.blade.php

همانطور که در فایل blade می بینید، یک متغیر $projects وجود دارد که از باطن ارسال می شود. این کل داده های پروژه است که برای فیلتر کردن وظایف در فرانت اند استفاده می شود.

React.js – ادغام اولیه

در این مقاله، ما فقط یک برنامه اصلی React.js خواهیم داشت که با لاراول کار می کند.

در ابتدا، ایده خوبی است که منابع غیر ضروری مانند resources/css پیش فرض و دایرکتوری های resources/js را حذف کنید.

یک فایل resources/react/app.tsx مانند این ایجاد کنید:

 import ReactDOM from 'react-dom/client'; import Main from "./Main"; import './index.css' ReactDOM.createRoot(document.getElementById('app')).render( <Main /> );
resources/react/app.tsx

پس ، پوشه resources/react ، دایرکتوری ریشه برای همه چیزهای React آینده خواهد بود.

یک index.css با محتوای موقت ایجاد کنید:

 .test-class { color: red; }
resources/react/index.css

همچنین یک Main.tsx با مقداری محتوای موقت ایجاد کنید:

 function Main() { return ( <div> <h2 className="test-class">React App</h2> </div> ); } export default Main;
resources/react/Main.tsx

برای تحلیل نتیجه در مرورگر، مطمئن شوید که پشتیبان در حال اجرا هستید و دارایی ها را از طریق ابزار vite بسازید:

 npm run build

یا اگر می خواهید تغییرات فایل React را مشاهده کنید و دارایی ها را به طور خودکار بسازید، می توانید این دستور را در حال اجرا نگه دارید:

 npm run dev

دو دستور npm run در بالا به vite اشاره دارد که دارایی ها را می سازد.
با تحلیل فایل package.json ، فیلد "scripts" می توانید این را ببینید:

 "scripts": { "dev": "vite", "build": "vite build" }

اکنون می توانید http://localhost:8000 را باز کنید تا نمای اولیه رندر شده را ببینید:

تصویر-85
اسکرین شات از مرورگر

نحوه اضافه کردن CSS

اکنون، هنگامی که Vite را راه اندازی کردید و React را در برنامه لاراول خود ادغام کردید، می توانید روی قسمت React کار کنید.

ما زمان زیادی را صرف سبک‌ها نمی‌کنیم، پس می‌توانید این CSS را در index.css خود جای‌گذاری کنید:

 body { background-color: whitesmoke; color: rgba(255, 255, 255, 0.7); font-family: "Montserrat", sans-serif; cursor: default; margin: auto 0; } /* MODAL start */ .modal-content { display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #fff; padding: 20px; border-radius: 5px; width: 500px; color: #2d3748; } .modal-header { font-size: 1.5rem; font-weight: 600; margin-bottom: 20px; } .modal-input-header { font-weight: 600; margin-bottom: 10px; } .modal-input { width: 100%; height: 30px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; } .modal-textarea { width: 100%; height: 100px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; margin-bottom: 20px; resize: vertical; } .modal-actions { display: flex; justify-content: space-between; align-items: center; width: 100%; } .modal-btn { padding: 10px 20px; border-radius: 5px; cursor: pointer; } .modal-btn-cancel { background-color: #e53e3e; color: #fff; border: none; } .modal-btn-cancel:hover { background-color: #c53030; } .modal-btn-submit { background-color: #2d3748; color: #fff; border: none; } .modal-btn-submit:hover { background-color: #4a5568; } .modal-question { font-size: 1.2rem; font-weight: 600; margin-bottom: 20px; } /* MODAL end */ /* LEFT & RIGHT SIDE start */ .left-side { width: 50%; float: left; } .right-side { width: 50%; float: left; } /* LEFT & RIGHT SIDE end */ /* LEFT SIDE start */ .left-side .no-tasks { font-size: 1.2rem; font-weight: 600; margin-bottom: 20px; text-align: center; color: #2d3748; } .left-side .task-item { padding: 10px; margin: 10px; min-height: 20px; min-width: 200px; color: #2d3748; list-style-type: none; } .left-side .task-item-content { display: flex; justify-content: space-between; align-items: center; } .left-side .task-project { display: flex; flex-direction: column; justify-content: center; align-items: center; margin-right: 10px; } .left-side .task-project-name { font-size: 1.5rem; font-weight: 600; margin-bottom: 5px; } .left-side .task-time { font-size: 0.8rem; } .left-side .task-title { font-size: 1.2rem; } .left-side .task-actions { display: flex; justify-content: space-between; align-items: center; } .left-side .task-edit-btn { background-color: #2d3748; color: #fff; border: none; border-radius: 5px; padding: 5px 10px; cursor: pointer; margin: 0 5px; } .left-side .task-edit-btn:hover { background-color: #4a5568; } .left-side .task-delete-btn { background-color: #e53e3e; color: #fff; border: none; border-radius: 5px; padding: 5px 10px; cursor: pointer; margin: 0 5px; } .left-side .task-delete-btn:hover { background-color: #c53030; } /* LEFT SIDE end */ /* RIGHT SIDE start */ .right-side .projects { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .right-side .projects-select { width: 100%; height: 30px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; } .right-side .no-project-selected { font-size: 1.2rem; font-weight: 600; margin-bottom: 20px; } .right-side .right-side-wrapper { display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #fff; padding: 20px; border-radius: 5px; color: #2d3748; } .right-side .add-task-header { font-size: 1.5rem; font-weight: 600; margin-bottom: 20px; } .right-side .add-task-input-header { font-weight: 600; margin-bottom: 10px; } .right-side .add-task-input { width: 100%; height: 30px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; } .right-side .add-task-textarea { width: 100%; height: 100px; border: 1px solid #ccc; border-radius: 5px; padding: 5px; margin-bottom: 20px; resize: vertical; } .right-side .add-task-actions { display: flex; justify-content: space-between; align-items: center; width: 100%; } .right-side .add-task-btn { padding: 10px 20px; border-radius: 5px; cursor: pointer; } .right-side .add-task-btn-cancel { background-color: #e53e3e; color: #fff; border: none; } .right-side .add-task-btn-cancel:hover { background-color: #c53030; } .right-side .add-task-btn-submit { background-color: #2d3748; color: #fff; border: none; } .right-side .add-task-btn-submit:hover { background-color: #4a5568; } /* RIGHT SIDE end */
منابع/React/index.css

بعداً پرونده index.css را در مؤلفه اصلی خود پیوست خواهید کرد.

خدماتی برای درخواست های API

همانطور که در پس زمینه انجام دادید ، در اینجا در جبهه نیز می توانید تمام پیاده سازی های منطقی را به یک فایل متفاوت منتقل کنید ، پس کد شما قابل خواندن و حفظ تر خواهد بود. ما می توانیم آن utils.ts فایل را نامگذاری کنیم ، زیرا در آن نیازهای لازم وجود خواهد داشت.

قبل از آن ، فقط برای پیکربندی Global Axios ، که در utils.ts استفاده می کنید ، axiosConfig.ts ایجاد کنید.

 import axios from 'axios'; export default axios.create({ baseURL: '/api' });
منابع/واکنش/axiosconfig.ts

با استفاده از تنظیمات فوق ، می توانید مطمئن باشید که تمام درخواست های HTTP دارای پیشوند /api هستند.

به عنوان مثال ، اگر از axiosConfig.get('/example') استفاده می کنید ، درخواست GET را به /api/example ارسال می کند. این یک پیکربندی اختیاری است ، اما یک روش توصیه شده برای داشتن کد غیر تکراری است.

از آنجا که شما برای ارسال درخواست های HTTP به سرور ، چند مورد استفاده خواهید داشت ، می توانید برای این عملیات پرونده های برنامه های جداگانه ای داشته باشید:

یک کار جدید برای یک پروژه ایجاد کنید

یک کار را به روز کنید

وظایف پروژه را فهرست کنید

یک کار را حذف کنید

وظایف پروژه دوباره

پس در زیر پرونده utils.ts قرار دارد:

 import axiosConfig from './axiosConfig'; import { toast } from 'react-toastify'; export const getErrorMessage = (error: unknown) => { if (error instanceof Error) return error.message; return String(error) } export const getTasks = async (projectId) => { if (!projectId) { toast.error("Project is required!"); return; } try { const response = await axiosConfig.get(`/tasks?project_id=${projectId}`); const { success, tasks, message } = response.data; if (success) { return tasks; } else { toast.error(message); return []; } } catch (err) { toast.error(getErrorMessage(err)); return []; } } export const reorderTasks = async (projectId, start, end) => { try { const response = await axiosConfig.put('/tasks', { project_id: projectId, start, end, }); const { success, message } = response.data; toast[success ? 'success' : 'error'](message); } catch (err) { toast.error(getErrorMessage(err)); } } export const editTask = async (task) => { if (!task.id) return; if (!task.title) { toast.error("Title is required!"); return; } try { const response = await axiosConfig.put(`/tasks/${task.id}`, { title: task.title, description: task.description, }); const { success, message } = response.data; toast[success ? 'success' : 'error'](message); } catch (err) { toast.error(getErrorMessage(err)); } } export const deleteTask = async (id) => { if (!id) { toast.error("Invalid task!"); return; } try { const response = await axiosConfig.delete(`/tasks/${id}`); const { success, message } = response.data; toast[success ? 'success' : 'error'](message); } catch (err) { toast.error(getErrorMessage(err)); } } export const createTask = async (task, projectId) => { if (!projectId) { toast.error("Project is required!"); return; } if (!task.title) { toast.error("Title is required!"); return; } try { const response = await axiosConfig.post(`/tasks?project_id=${projectId}`, { title: task.title, description: task.description, }); const { success, message } = response.data; toast[success ? 'success' : 'error'](message); } catch (err) { toast.error(getErrorMessage(err)); } }
منابع/واکنش/use.ts

در پرونده فوق توابع زیر را پیدا خواهید کرد:

geterrormessage : اگر ورودی نمونه ای از خطا باشد ، پیام خطا را برمی گرداند - در غیر این صورت ، آن را به یک رشته تبدیل می کند.

GetTasks : وظایف را برای شناسه پروژه معین با استفاده از AxiOS بازیابی می کند. اگر شناسه پروژه از دست رفته باشد یا درخواست API ناموفق باشد ، نان تست خطایی را نشان می دهد.

reordertasks : یک درخواست PUT را برای تنظیم مجدد وظایف در یک پروژه بر اساس موقعیت های شروع و پایان ارسال می کند. بر اساس پاسخ API یک نان تست موفقیت یا خطا را نشان می دهد.

edittask : برای به روزرسانی اطلاعات کار ، یک درخواست Put ارسال می کند. تأیید می کند که این کار قبل از درخواست ، دارای شناسه و عنوان است. بر اساس پاسخ API یک نان تست موفقیت یا خطا را نشان می دهد.

DeletEtask : درخواست حذف را برای حذف یک کار توسط شناسه خود ارسال می کند. بر اساس پاسخ API یک نان تست موفقیت یا خطا را نشان می دهد.

createTask : یک درخواست پست برای ایجاد یک کار جدید برای شناسه پروژه خاص ارسال می کند. تأیید می کند که شناسه پروژه موجود است و وظیفه قبل از درخواست ، دارای عنوان است. بر اساس پاسخ API یک نان تست موفقیت یا خطا را نشان می دهد.

react.js اجزای

اکنون ، از آنجا که شما برنامه های کاربردی را آماده کرده اید ، در پوشه resources/react/components ، می توانید مؤلفه هایی را که برای استفاده در Main.tsx خود استفاده می کنید ایجاد کنید.

ابتدا ، SelectProject.tsx ایجاد کنید ، که مسئولیت انتخاب پروژه فعلی را بر عهده خواهد داشت:

 import {getTasks} from "../utils"; function SelectProject({projectId, projects, setProjectId, setTasks}) { const selectProject = (e) => { const value = e.target.value; setProjectId(value); if (value === '') { setTasks([]); } else { getTasks(value).then((tasksData) => setTasks(tasksData)); } }; return ( <div className="projects"> <select className="projects-select" value={projectId} onChange={selectProject}> <option value="" defaultValue>Choose a project</option> {projects.map((project) => ( <option key={project.id} value={project.id}>{project.name}</option> ))} </select> </div> ); } export default SelectProject;
منابع/واکنش/مؤلفه ها/selectproject.tsx

مؤلفه SelectProject منوی کشویی را ارائه می دهد که به کاربر امکان می دهد یک پروژه را انتخاب کند. هنگامی که یک پروژه انتخاب شد ، حالت را با شناسه پروژه انتخاب شده به روز می کند ، با استفاده از عملکرد ابزار getTasks ، وظایف آن پروژه را واگذار می کند و حالت را با کارهای بازیابی شده به روز می کند و تعامل پویا را با انتخاب پروژه و بارگیری کار ارائه می دهد.

سپس TaskList.tsx ایجاد کنید ، که مسئولیت ارائه وظایف پروژه و دستکاری های کشیدن و رها کردن آنها را بر عهده خواهد داشت:

 import {DragDropContext, Draggable, Droppable} from "react-beautiful-dnd"; import Task from "./Task"; import {reorderTasks} from "../utils"; const getItemStyle = (isDragging, draggableStyle) => ({ background: isDragging ? 'lightgreen' : 'grey', ...draggableStyle, }); function TaskList({ tasks, setIsModalEditOpen, setModalEditTask, setIsModalDeleteOpen, setModalDeleteTaskId, projectId, setTasks }) { const handleDragEnd = (result) => { if (!result.destination || result.destination.index === result.source.index) { return; } const items = Array.from(tasks); const [reorderedItem] = items.splice(result.source.index, 1); items.splice(result.destination.index, 0, reorderedItem); reorderTasks(projectId, result.source.index + 1, result.destination.index + 1); setTasks(items); }; return ( <DragDropContext onDragEnd={handleDragEnd}> <Droppable droppableId="droppable"> {(provided) => ( <ul {...provided.droppableProps} ref={provided.innerRef}> {tasks.map((task, index) => ( <Draggable key={task.id.toString()} draggableId={task.id.toString()} index={index}> {(provided, snapshot) => ( <li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} className="task-item" style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)} > <Task task={task} setIsModalEditOpen={setIsModalEditOpen} setModalEditTask={setModalEditTask} setIsModalDeleteOpen={setIsModalDeleteOpen} setModalDeleteTaskId={setModalDeleteTaskId} /> </li> )} </Draggable> ))} {provided.placeholder} </ul> )} </Droppable> </DragDropContext> ); } export default TaskList;
منابع/واکنش/مؤلفه ها/لیست کار.

مؤلفه TaskList از کتابخانه react-beautiful-dnd برای اجرای یک فهرست کار قابل کشیدن استفاده می کند. این فهرست ی از کارها را ارائه می دهد و به کاربران امکان می دهد تا وظایف خود را برای مرتب کردن آنها بکشید و رها کنند ، با عملکرد کشیدن و رها کردن باعث ایجاد یک عملکرد ( handleDragEnd ) می شود که ترتیب کار را هم از نظر بصری و هم در پس زمینه با استفاده از عملکرد برنامه کاربردی reorderTasks به روز می کند.

اکنون ، Task.tsx ایجاد کنید ، که مسئولیت یک کار واحد از یک فهرست را بر عهده خواهد داشت:

 function Task({ task, setIsModalEditOpen, setModalEditTask, setIsModalDeleteOpen, setModalDeleteTaskId }) { const handleEdit = () => { setModalEditTask(task); setIsModalEditOpen(true); }; const handleDelete = () => { setModalDeleteTaskId(task.id); setIsModalDeleteOpen(true); }; return ( <div className="task-item-content"> <span className="task-project"> <span className="task-project-name"> {task.project.name} </span> <span className="task-time"> Created {task.created} </span> </span> <span className="task-title">{task.title}</span> <div className="task-actions"> <button className="task-edit-btn" onClick={handleEdit}>Edit</button> <button className="task-delete-btn" onClick={handleDelete}>Delete</button> </div> </div> ); } export default Task;
منابع/واکنش/مؤلفه ها/کار

مؤلفه Task یک مورد کار واحد را نشان می دهد. این جزئیات وظیفه از جمله نام پروژه ، زمان ایجاد و عنوان را نشان می دهد و دکمه هایی را برای ایجاد اقدامات مانند ویرایش و حذف کار ، با دستگیره های مربوطه ( handleEdit و handleDelete ) فراهم می کند.

در مرحله بعد ، AddTaskForm.tsx ایجاد کنید ، که مسئولیت فرم کار برای گفت ن کارها به پروژه منتخب فعلی را بر عهده خواهد داشت:

 import {createTask} from "../utils"; function AddTaskForm({newTask, setNewTask, projectId, reloadTasks }) { const clearTaskCreate = () => { setNewTask({title: '', description: ''}); }; const submitTaskCreate = () => { createTask(newTask, projectId).then(() => { setNewTask({title: '', description: ''}); reloadTasks(); }); }; return ( <> <h2 className="add-task-header">Add Task</h2> <h3 className="add-task-header">Title</h3> <input type="text" className="add-task-input" onChange={(e) => setNewTask({ ...newTask, title: e.target.value })} value={newTask.title} /> <h3 className="add-task-input-header">Description</h3> <textarea className="add-task-textarea" onChange={(e) => setNewTask({ ...newTask, description: e.target.value })} value={newTask.description || ''} /> <div className="add-task-actions"> <button className="add-task-btn add-task-btn-cancel" onClick={clearTaskCreate}>Clear </button> <button className="add-task-btn add-task-btn-submit" onClick={submitTaskCreate}>Add</button> </div> </> ); } export default AddTaskForm;
منابع/واکنش/اجزای/addTaskForm.tsx

مؤلفه AddTaskForm فرم اضافه کردن کارهای جدید را فراهم می کند. این شامل زمینه های ورودی برای عنوان و توضیحات کار ، با دکمه هایی برای پاک کردن فرم یا ارسال کارآفرینی است ، و از عملکرد ابزار createTask برای رسیدگی به روند ایجاد کار استفاده می کند و باعث موفقیت مجدد کارها می شود.

سپس ، ModalEdit.tsx ایجاد کنید ، که مسئولیت پنجره معین برای ویرایش و ارسال تغییرات به یک کار را بر عهده خواهد داشت:

 import {Modal} from "react-responsive-modal"; import React from "react"; import {editTask} from "../utils"; function ModalEdit({ isModalEditOpen, setIsModalEditOpen, setModalEditTask, modalEditTask, reloadTasks }) { const submitTaskEdit = () => { setIsModalEditOpen(false); editTask(modalEditTask).then(() => { reloadTasks(); }); }; return ( <Modal open={isModalEditOpen} center onClose={() => setIsModalEditOpen(false)}> <div className="modal-content"> <h2 className="modal-header">Edit Task</h2> <h3 className="modal-input-header">Title</h3> <input type="text" value={modalEditTask.title} className="modal-input" onChange={(e) => setModalEditTask({ ...modalEditTask, title: e.target.value })} /> <h3 className="modal-input-header">Description</h3> <textarea className="modal-textarea" onChange={(e) => setModalEditTask({ ...modalEditTask, description: e.target.value })} value={modalEditTask.description || ''} /> <div className="modal-actions"> <button className="modal-btn modal-btn-cancel" onClick={() => setIsModalEditOpen(false)} >Close </button> <button className="modal-btn modal-btn-submit" onClick={submitTaskEdit} >Save</button> </div> </div> </Modal> ) } export default ModalEdit;
منابع/واکنش/مؤلفه ها/modaledit.tsx

مؤلفه ModalEdit برای ویرایش جزئیات کار یک معین را نشان می دهد. این شامل زمینه های ورودی برای اصلاح عنوان و توضیحات کار و دکمه ها برای بستن معین یا ذخیره تغییرات ، با استفاده از تابع Utility editTask برای رسیدگی به فرآیند ویرایش کار و ایجاد بارگیری مجدد از کارها پس از ویرایش موفقیت آمیز است.

در مرحله بعد ، ModalDelete.tsx ایجاد کنید ، که مسئولیت ارسال یک کار را بر عهده خواهد داشت:

 import {Modal} from "react-responsive-modal"; import {deleteTask} from "../utils"; function ModalDelete({ isModalDeleteOpen, setIsModalDeleteOpen, modalDeleteTaskId, reloadTasks }) { const submitTaskDelete = () => { setIsModalDeleteOpen(false); deleteTask(modalDeleteTaskId).then(() => { reloadTasks(); }); }; return ( <Modal open={isModalDeleteOpen} onClose={() => setIsModalDeleteOpen(false)} center> <div className="modal-content"> <h2 className="modal-header">Delete Task</h2> <p className="modal-question"> Are you sure you want to delete this task? </p> <div className="modal-actions"> <button className="modal-btn modal-btn-cancel" onClick={() => setIsModalDeleteOpen(false)} >Cancel</button> <button className="modal-btn modal-btn-submit" onClick={submitTaskDelete} >Yes</button> </div> </div> </Modal> ); } export default ModalDelete;
منابع/واکنش/مؤلفه ها/modaldelete.tsx

مؤلفه ModalDelete برای تأیید حذف یک کار یک معین را نشان می دهد. این گزینه برای لغو حذف یا حذف کار ، با استفاده از عملکرد ابزار deleteTask و ایجاد بارگیری مجدد وظایف پس از حذف موفقیت آمیز ، گزینه هایی را فراهم می کند.

و در آخر ، Main.tsx را با استفاده از اجزای تعریف شده فوق تنظیم کنید.

 import { useState } from 'react'; import {getTasks} from "./utils"; import "react-responsive-modal/styles.css"; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import ModalEdit from "./components/ModalEdit"; import ModalDelete from "./components/ModalDelete"; import TaskList from "./components/TaskList"; import SelectProject from "./components/SelectProject"; import AddTaskForm from "./components/AddTaskForm"; function Main () { const [projectId, setProjectId] = useState(''); const projectsData = document.getElementById('app').getAttribute('data-projects'); const projects = JSON.parse(projectsData); const [tasks, setTasks] = useState([]); const [isModalEditOpen, setIsModalEditOpen] = useState(false); const [modalEditTask, setModalEditTask] = useState({id: '', title: '', description: ''}); const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false); const [modalDeleteTaskId, setModalDeleteTaskId] = useState(''); const [newTask, setNewTask] = useState({title: '', description: ''}); const reloadTasks = () => { getTasks(projectId).then((tasksData) => setTasks(tasksData)); }; return ( <div> <ToastContainer autoClose={2000} /> <ModalEdit isModalEditOpen={isModalEditOpen} setIsModalEditOpen={setIsModalEditOpen} modalEditTask={modalEditTask} setModalEditTask={setModalEditTask} reloadTasks={reloadTasks} /> <ModalDelete isModalDeleteOpen={isModalDeleteOpen} setIsModalDeleteOpen={setIsModalDeleteOpen} modalDeleteTaskId={modalDeleteTaskId} reloadTasks={reloadTasks} /> <div className="left-side"> {tasks.length > 0 ? ( <TaskList tasks={tasks} setIsModalEditOpen={setIsModalEditOpen} setModalEditTask={setModalEditTask} setIsModalDeleteOpen={setIsModalDeleteOpen} setModalDeleteTaskId={setModalDeleteTaskId} projectId={projectId} setTasks={setTasks} /> ) : ( <div className="no-tasks"> {projectId === '' ? ( <p>Choose a project to see its tasks.</p> ) : ( <p>This project has no tasks.</p> )} </div> )} </div> <div className="right-side"> <div className="right-side-wrapper"> <SelectProject projectId={projectId} projects={projects} setProjectId={setProjectId} setTasks={setTasks} /> {projectId === '' ? ( <div className="no-project-selected"> <p>Please select a project.</p> </div> ) : ( <AddTaskForm newTask={newTask} setNewTask={setNewTask} projectId={projectId} reloadTasks={reloadTasks} /> )} </div> </div> </div> ); } export default Main;
منابع/React/Main.tsx

مؤلفه Main یک مؤلفه اصلی است که مدیریت پروژه و عملکردهای مربوط به کار را انجام می دهد. این شامل مدالها برای ویرایش و حذف وظایف ، یک فهرست کار با به روزرسانی های پویا ، کشویی انتخاب پروژه و فرم اضافه کردن کارهای جدید ، استفاده از مدیریت حالت و عملکردهای ابزار برای تعامل کاربر صاف است.

نتایج نهایی

در این مرحله ، تمام مؤلفه ها آماده تعامل با یکدیگر هستند. پس می توانید دارایی های جلو را بسازید و سرور را اجرا کنید:

 npm run build && php artisan serve

با مراجعه به http://127.0.0.1:8000 این نوع نتیجه را می گیرید:

فهرست وظیفه یا فهرست  کار
GIF از یک پروژه کاری محلی تولید می شود

خودشه!

اکنون می توانید به راحتی react.js را در برنامه Laravel خود ادغام کنید بدون استفاده از ابزارهای اضافی LARAVEL (مانند اینرسی). و در نتیجه ، شما می توانید همچنان برنامه Laravel خود را برای ساختن API های مقیاس پذیر تر با تأیید اعتبار و سایر موارد خود ادامه دهید.

پس ، این می تواند فقط یک برنامه نمونه برای پروژه بعدی LaRavel و React.js بعدی شما باشد.

نتیجه

با استفاده از این مقاله ، شما یک برنامه فهرست تک صفحه ای و کامل با استفاده از React.js (با TypeScript) با Vite.js به عنوان فناوری های جلو ، لاراول به عنوان یک چارچوب باطن و بسته react-beautiful-dnd برای داشتن موارد قابل کشیدن ساخته اید.
اکنون می دانید که چگونه به صورت دستی React.js را در برنامه Laravel خود ادغام کرده و آن را حفظ کنید.

شما می توانید کد کامل پروژه را در اینجا در GitHub من پیدا کنید ، جایی که من به طور فعال بسیاری از کارهایم را در مورد فن آوری های مختلف مدرن تبلیغ می کنم.
برای اطلاعات بیشتر ، می توانید به وب سایت من مراجعه کنید: Boosfalse.com

احساس راحتی کنید که این مقاله را به اشتراک بگذارید. 😇

خبرکاو

ارسال نظر




تبليغات ايهنا تبليغات ايهنا

تمامی حقوق مادی و معنوی این سایت متعلق به خبرکاو است و استفاده از مطالب با ذکر منبع بلامانع است