در این مقاله به نحوه ساخت فروشگاه اینترنتی با لاراول و vue خواهیم پرداخت. پس از این آموزش، باید بدانید که چگونه از لاراول و ویو برای ساخت یک اپلیکیشن تحت وب استفاده کنید و اصول ساخت فروشگاه اینترنتی را بدانید.
با ظهور اینترنت، بسیاری از کارهای عادی سریعتر و آسان تر شدند. یکی از چیزهایی که بهبود یافته، تجارت است. فعالیت های تجاری انجام شده در وب به عنوان تجارت الکترونیک شناخته می شود.
در این آموزش قصد داریم مراحل ساخت یک فروشگاه اینترنتی با لاراول (Laravel) و ویو (Vue) را از صفر تا صد به شما آموزش دهیم. فقط باید دقت داشته باشید که این فروشگاه اینترنتی قابلیت های ساده ای مثل نمایش محصولات، خرید محصول و ... را دارد. در نهایت وقتی کار ما تمام شد، فروشگاهی به شکل زیر خواهیم داشت:
نکته بسیار مهم اینجاست که اگر در هر یک از زمینه های فوق دانشی ندارید کافیست روی لینک های متصل شده به عبارات کلیک کرده و دوره های آموزشی پروژه محور مرتبط با آنها را ملاحظه بفرمایید.
همانطور که قبلا اشاره کردیم، یک فروشگاه، فروش کالا و خدمات آنلاین را بسیار راحت می کند. فهرستی از محصولاتی را که فروشنده میخواهد بفروشد و قیمتهای آنها را نشان میدهد، سپس صفحهای را نمایش می دهد که در آن میتوانید تمام جزئیات یک محصول انتخاب شده را مشاهده کنید، و در نهایت، هزینه محصول را پرداخت کنید و نحوه دریافت آن را مشخص کنید.
با در نظر گرفتن این موضوع، می دانیم که برای داشتن یک اپلیکیشن تجارت الکترونیکی مفید، باید موارد زیر را توسعه دهیم:
این برنامه دارای دو نوع کاربر است:
حتما می دانید برنامه محصولاتی دارد که باید ذخیره شوند. این محصولات سفارش داده می شوند و شما به راهی برای ذخیره و پیگیری این سفارش ها نیاز دارید. این مراحل اساس هر پلتفرم تجارت الکترونیکی را تشکیل می دهند.
جزئیات اساسی که باید در قسمت های مختلف ذخیره کنیم:
Table |
Fields |
User |
name, email, password, is_admin
|
Product |
name, description, price, units, image
|
Order |
product, user, quantity, address, is_delivered |
عملیات اساسی که برنامه باید آن ها را انجام دهد، و اینکه این کارها در کجا باید انجام شوند، عبارتند از:
Operation | Controller |
Login | UserController |
Register | UserController |
User profile | UserController |
View all orders by a single user | UserController |
View product listing | ProductController |
View a single product | ProductController |
Edit a product | ProductController |
Add a new product | ProductController |
Add more units to a product | ProductController |
Remove a product | ProductController |
Order product | OrderController |
View all orders | OrderController |
View a single order information | OrderController |
Deliver an order | OrderController |
Delete an order | OrderController |
اگر میخواهید کمی دقیقتر باشید، میتوانید به اضافه کردن دسته بندی ها، برچسبها، اقلام موجود در انبار، انواع کاربر بیشتر و بسیاری موارد دیگر فکر کنید. با این حال، ما آنها را برای این راهنما در نظر نخواهیم گرفت، اما شما قطعا باید آن را به تنهایی امتحان کنید.
یک راه خوب برای یادگیری، تمرین است. با انجام تکلیف پاراگراف قبل می توانید ذهن خود را برای چالش های مختلف نیرومند کنید.
ما قصد داریم از Laravel CLI برای ایجاد یک برنامه جدید Laravel استفاده کنیم. برای ایجاد یک برنامه جدید لاراول، دستور زیر را اجرا کنید:
laravel new bigStore
سپس می توانید به پروژه ای که ایجاد کردیم وارد شوید. دستورات مربوط به لاراول که در طول مقاله اجرا می کنیم باید در پوشه روت پروژه لاراول اجرا شوند.
مدل های لاراول راه بسیار مناسبی برای تعامل با دیتابیس ارائه می دهند. آنها متدهایی را برای تمام عملیات اساسی که برای اجرای جدول دیتابیس خود نیاز دارید ارائه می کنند.
بیایید مدل هایی بسازیم که با دیتابیس ما تعامل داشته باشند و منطق کسب و کار را حفظ کنند. نصبهای جدید لاراول با مدل کاربر خارج از جعبه ارائه میشوند که میتوان آن را در دایرکتوری app پیدا کرد، بنابراین بیایید دو مدل دیگر را بسازیم:
php artisan make:model Product -mr
ما فلگ mr- را به دستور make:model
اضافه کردهایم تا مایگریشن و کنترلری را برای مدل ایجاد کند.
php artisan make:model Order -mr
سپس فایل app/User.php
را باز کنید و محتویات آن را با کدهای زیر جایگزین کنید:
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable, SoftDeletes; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', ]; public function orders() { return $this->hasMany(Order::class); } }
ما از SoftDeletes
استفاده می کنیم تا به ما این امکان را بدهد که یک رکورد دیتا بیس را به عنوان حذف شده علامت گذاری کنیم بدون اینکه واقعا آن را حذف کنیم. اگر می خواهید داده ها را بازیابی کنید، این کار مفید است.
ما همچنین یک آرایه به نام fillable به نام ستونهایی که میخواهیم به صورت انبوه به جدول کاربران اختصاص دهیم، داریم. تخصیص انبوه زمانی اتفاق می افتد که مدل User خود را به صورت ایستا فراخوانی کنیم و یک آرایه را به متد ایجاد آن ارسال کنیم.
فایل app/Product.php
را باز کرده و به صورت زیر ویرایش کنید:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Product extends Model { use SoftDeletes; protected $fillable = [ 'name', 'price', 'units', 'description', 'image' ]; public function orders(){ return $this->hasMany(Order::class); } }
مدل Product کاملا شبیه مدل کاربر است. دارای آرایه fillable و همچنین یک متد orders برای ایجاد رابطه با سفارشات قرار داده شده در برنامه است. اکنون فایل app/Order.php
را باز کرده و به صورت زیر ویرایش کنید:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Order extends Model { use SoftDeletes; protected $fillable = [ 'product_id', 'user_id', 'quantity', 'address' ]; public function user() { return $this->belongsTo(User::class, 'user_id'); } public function product() { return $this->belongsTo(Product::class, 'product_id'); } }
مدل Order کمی متفاوت از دو مدل دیگر به نظر می رسد اما در اصل یک چیز است. ما به تازگی نوع دیگری از رابطه را ایجاد کردیم. belongsTo که نشان می دهد کدام کاربر سفارشی را انجام داده یا کدام محصول را سفارش داده است.
مهاجرت ها راه خوبی برای ایجاد و نگهداری دیتابیس برنامه شما هستند. آنها نحوه ایجاد یا تغییر جدول ها را مشخص می کند.
مهاجرت ها مفید هستند زیرا به شما در مدیریت جدول ها، ستون ها و کلیدهای دیتابیس کمک می کنند. میتوانید فایلهای مهاجرت را به جای SQL به اشتراک بگذارید، و از آنجایی که فایلهای مهاجرت به صورت زمانی اجرا میشوند، کار با git را آسان میکنند، بنابراین برای تیمها عالی است.
برای ارتقاء سطح دانش خود در زمینه SQL به شما توصیه می کنیم که دوره آموزش SQL و MySQL را حتما ببینید.
فایل create_users_table
را در دایرکتوری دیتابیس/migrations باز کنید و محتوا را با موارد زیر جایگزین کنید:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->boolean('is_admin')->default(false); $table->string('password'); $table->rememberToken(); $table->timestamps(); $table->softDeletes(); }); } public function down() { Schema::dropIfExists('users'); } }
لاراول کاملا خوانا است و احتمالا می توانید با خواندن کد متوجه شوید که چه اتفاقی در حال رخ دادن است. ما تعریف کردیم که کدام ستون ها و ویژگی های آنها باید در جدول وجود داشته باشند. در کلاس Blueprint متدهای زیادی برای مهاجرت وجود دارد. در اینجا می توانید بیشتر در مورد آن بخوانید. سپس، فایل create_products_table
را در دایرکتوری database/migrations
باز کنید و کد زیر را جایگزین کنید:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateProductsTable extends Migration { public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('description'); $table->unsignedInteger('units')->default(0); $table->double('price'); $table->string('image'); $table->timestamps(); $table->softDeletes(); }); } public function down() { Schema::dropIfExists('products'); } }
در نهایت، فایل create_orders_table
را در دایرکتوری database/migrations
باز کنید و محتویات آن را با موارد زیر جایگزین کنید:
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateOrdersTable extends Migration { public function up() { Schema::create('orders', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('product_id'); $table->unsignedInteger('user_id'); $table->unsignedInteger('quantity')->default(1); $table->string('address')->nullable(); $table->boolean('is_delivered')->default(false); $table->timestamps(); $table->softDeletes(); }); } public function down() { Schema::dropIfExists('orders'); } }
seeder ها یک راه عالی برای پر کردن دیتابیس ما با داده های ساختگی است. ما قصد داریم از کلاس seeder برای ایجاد حساب کاربری برای مدیریت خود استفاده کنیم.
با اجرای دستور زیر یک کلاس seeder ایجاد کنید:
php artisan make:seed UsersTableSeeder
اکنون فایل UserTableSeeder.php
را در دایرکتوری database/seeds
باز کرده و محتوا را با موارد زیر جایگزین کنید:
<?php use App\User; use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { public function run() { $user = new User; $user->name = "Admin"; $user->email = "admin@devtest.com"; $user->password = bcrypt('secret'); $user->is_admin = true; $user->save(); } }
کلاس Seeder بالا یک کاربر مدیریت جدید در دیتابیس ایجاد می کند. به یاد بیاورید که وقتی مدل User را تعریف کردیم، is_admin
را در ستون fillable
قرار ندادیم. دلیل آن این است که ما نمی خواهیم کسی که برنامه ما را جعل کند، یک کاربر از نوع is_admin
ایجاد کند. به همین دلیل است که ما مجبور شدیم یک نمونه کاربری در اینجا ایجاد کنیم تا بتوانیم به تمام ستون ها از جدول User دسترسی داشته باشیم.
یک کلاس Seeder دیگر برای جدول محصولات ما می سازیم:
php artisan make:seed ProductsTableSeeder
فایل database/seeds/ProductTableSeeder.ph
p را باز کنید و محتویات آن را با موارد زیر جایگزین کنید:
<?php use Illuminate\Database\Seeder; class ProductsTableSeeder extends Seeder { public function run() { $products = [ [ 'name' => "MEN'S BETTER THAN NAKED & JACKET", 'description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua consequat.', 'units' => 21, 'price' => 200.10, 'image' => 'http://images.thenorthface.com/is/image/TheNorthFace/236x204_CLR/mens-better-than-naked-jacket-AVMH_LC9_hero.png', 'created_at' => new DateTime, 'updated_at' => null, ], [ 'name' => "WOMEN'S BETTER THAN NAKED™ JACKET", 'description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua consequat.', 'units' => 400, 'price' => 1600.21, 'image' => 'http://images.thenorthface.com/is/image/TheNorthFace/236x204_CLR/womens-better-than-naked-jacket-AVKL_NN4_hero.png', 'created_at' => new DateTime, 'updated_at' => null, ], [ 'name' => "WOMEN'S SINGLE-TRACK SHOE", 'description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua consequat.', 'units' => 37, 'price' => 378.00, 'image' => 'http://images.thenorthface.com/is/image/TheNorthFace/236x204_CLR/womens-single-track-shoe-ALQF_JM3_hero.png', 'created_at' => new DateTime, 'updated_at' => null, ], [ 'name' => 'Enduro Boa® Hydration Pack', 'description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua consequat.', 'units' => 10, 'price' => 21.10, 'image' => 'http://images.thenorthface.com/is/image/TheNorthFace/236x204_CLR/enduro-boa-hydration-pack-AJQZ_JK3_hero.png', 'created_at' => new DateTime, 'updated_at' => null, ] ]; DB::table('products')->insert($products); } }
داده هایی که ما دیتابیس را با آنها پر کردیم برای اهداف آموزشی است. ممکن است برای استفاده واقعی از تصاویر به مجوز نیاز داشته باشید. هنگامی که ساختن seeder را تمام کردید، باید دیتابیس/seeds/DatabaseSeeder.php
را تغییر دهید، که در واقع seeders را فراخوانی می کند:
<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run() { $this->call([ UsersTableSeeder::class, ProductsTableSeeder::class, ]); } }
اکنون، هنگامی که دستور seed دیتابیس خود را اجرا می کنیم، کلاس DatabaseSeeder
فراخوانی می شود و run را فراخوانی می کند که به نوبه خود کلاس های seeder را که برای اجرا تنظیم کرده بودیم، فراخوانی می کند. در اینجا می توانید اطلاعات بیشتری در مورد seeder ها به دست آورید.
هر چیزی را که نیاز داریم دیتابیس ما داشته باشد تعریف کرده ایم. اکنون باید خود دیتابیس را تعریف کنیم. ما قصد داریم از SQLite برای این راهنما استفاده کنیم، اما شما می توانید از هر دیتابیسی که دوست دارید، استفاده کنید. اگر در محیط production هستید، باید از یک دیتابیس غیرفایلی (non-file) مانند MySQL استفاده کنید.
یک فایل دیتابیس در database/database.sqlite
ایجاد کنید. سپس فایل env خود را باز کنید و خطوط زیر را جایگزین کنید:
DB_CONNECTION=mysql DB_DATABASE=homestead DB_USERNAME=username DB_PASSWORD=password
با:
DB_CONNECTION=sqlite DB_DATABASE=/full/path/to/database/database.sqlite
این کدها برای راه اندازی دیتابیس ما است. مهاجرت ها را اجرا کنید تا جدول ها دیتابیس را برای برنامه ما ایجاد کرده و آن را بسازند:
php artisan migrate --seed
فروشگاه اینترنتی ما از Laravel و Vue برای ایجاد بهترین برنامه استفاده می کند. یعنی ما باید API هایی را برای ارائه کامپونت های Vue خود با داده تعریف کنیم.
لاراول به طور پیش فرض از مسیرهای وب و API پشتیبانی می کند. مسیرهای وب مسیریابی را برای صفحات ایجاد شده به صورت پویا که از یک مرورگر وب قابل دسترسی هستند، مدیریت میکنند، در حالی که مسیرهای API درخواستهای مشتریانی را که معمولا به پاسخ در قالب JSON یا XML نیاز دارند، رسیدگی میکنند.
برنامه ما برای اکثر درخواست ها دارای API خواهد بود. ما باید API های خود را ایمن کنیم تا مطمئن شویم فقط کاربران مجاز به آن دسترسی خواهند داشت. برای این کار از لاراول passport استفاده می کنیم.
برای نصب passport دستور زیر را اجرا کنید:
composer require laravel/passport
Laravel Passport با مهاجرت های خود همراه است، مهاجرت ها را با استفاده از دستور زیر اجرا کنید:
php artisan migrate
سپس، دستور نصب passport را اجرا کنید تا کلیدهای لازم برای ایمن سازی برنامه شما ایجاد شود:
php artisan passport:install
دستور بالا کلیدهای رمزگذاری مورد نیاز برای تولید توکن های دسترسی ایمن به اضافه کلاینت های " personal access" و " password grant" را ایجاد می کند که برای تولید access token ها استفاده می شود.
پس از نصب، باید از ویژگی Laravel Passport HasApiToken
در مدل User استفاده کنید. این ویژگی چند متد کمکی برای مدل شما ارائه می دهد که به شما امکان می دهد توکن و مجوزهای کاربر تایید شده را بررسی کنید.
فایل app/User.php
را باز کرده و به صورت زیر ویرایش کنید:
<?php namespace App; [...] use Laravel\Passport\HasApiTokens; use Illuminate\Database\Eloquent\SoftDeletes; class User extends Authenticatable { use Notifiable, SoftDeletes, HasApiTokens; [...] }
در مرحله بعد، متد Passport::routes
را در متد boot در AuthServiceProvider
را فراخوانی کنید. این متد مسیرهای لازم برای ایجاد توکن های مورد نیاز برنامه شما را ثبت می کند:
آپدیت فایل app/Providers/AuthServiceProvider.php
به صورت زیر است:
<?php [...] use Laravel\Passport\Passport; class AuthServiceProvider extends ServiceProvider { [...] public function boot() { $this->registerPolicies(); Passport::routes(); } [...] }
در نهایت، در فایل پیکربندی config/auth.php
، باید گزینه driver محافظ api authentication را با passport مقداردهی کنید.
[...] 'guards' => [ [...] 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], [...]
همه این کدها برای passport لاراول است.
در بالا، مدل های خود و کنترلرهای همراه آنها را تعریف کردیم. این کنترلرها در دایرکتوری app/Http/Controllers
قرار دارند. مدل User، یک کنترلر ندارد، بنابراین ابتدا می خواهیم آن را ایجاد کنیم. برای این کار را دستور زیر را اجرا کنید:
php artisan make:controller UserController
اکنون فایل کنترلر ایجاد شده app/Http/Controllers/UserController.php
را باز کنید و محتویات آن را با موارد زیر جایگزین کنید:
<?php namespace App\Http\Controllers; use Auth; use App\User; use Validator; use Illuminate\Http\Request; class UserController extends Controller { public function index() { return response()->json(User::with(['orders'])->get()); } public function login(Request $request) { $status = 401; $response = ['error' => 'Unauthorised']; if (Auth::attempt($request->only(['email', 'password']))) { $status = 200; $response = [ 'user' => Auth::user(), 'token' => Auth::user()->createToken('bigStore')->accessToken, ]; } return response()->json($response, $status); } public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|max:50', 'email' => 'required|email', 'password' => 'required|min:6', 'c_password' => 'required|same:password', ]); if ($validator->fails()) { return response()->json(['error' => $validator->errors()], 401); } $data = $request->only(['name', 'email', 'password']); $data['password'] = bcrypt($data['password']); $user = User::create($data); $user->is_admin = 0; return response()->json([ 'user' => $user, 'token' => $user->createToken('bigStore')->accessToken, ]); } public function show(User $user) { return response()->json($user); } public function showOrders(User $user) { return response()->json($user->orders()->with(['product'])->get()); } }
در بالا چند متد برای کلاس تعریف کردیم:
سپس فایل app/Http/Controllers/ProductController.php را باز کرده و محتویات آن را با موارد زیر جایگزین کنید:
<?php namespace App\Http\Controllers; use App\Product; use Illuminate\Http\Request; class ProductController extends Controller { public function index() { return response()->json(Product::all(),200); } public function store(Request $request) { $product = Product::create([ 'name' => $request->name, 'description' => $request->description, 'units' => $request->units, 'price' => $request->price, 'image' => $request->image ]); return response()->json([ 'status' => (bool) $product, 'data' => $product, 'message' => $product ? 'Product Created!' : 'Error Creating Product' ]); } public function show(Product $product) { return response()->json($product,200); } public function uploadFile(Request $request) { if($request->hasFile('image')){ $name = time()."_".$request->file('image')->getClientOriginalName(); $request->file('image')->move(public_path('images'), $name); } return response()->json(asset("images/$name"),201); } public function update(Request $request, Product $product) { $status = $product->update( $request->only(['name', 'description', 'units', 'price', 'image']) ); return response()->json([ 'status' => $status, 'message' => $status ? 'Product Updated!' : 'Error Updating Product' ]); } public function updateUnits(Request $request, Product $product) { $product->units = $product->units + $request->get('units'); $status = $product->save(); return response()->json([ 'status' => $status, 'message' => $status ? 'Units Added!' : 'Error Adding Product Units' ]); } public function destroy(Product $product) { $status = $product->delete(); return response()->json([ 'status' => $status, 'message' => $status ? 'Product Deleted!' : 'Error Deleting Product' ]); } }
در ProductController بالا ما هفت متد را تعریف کردیم:
سپس فایل app/Http/Controllers/OrderController.php
را باز کنید و محتوای زیر را جایگزین کنید:
<?php namespace App\Http\Controllers; use App\Order; use Auth; use Illuminate\Http\Request; class OrderController extends Controller { public function index() { return response()->json(Order::with(['product'])->get(),200); } public function deliverOrder(Order $order) { $order->is_delivered = true; $status = $order->save(); return response()->json([ 'status' => $status, 'data' => $order, 'message' => $status ? 'Order Delivered!' : 'Error Delivering Order' ]); } public function store(Request $request) { $order = Order::create([ 'product_id' => $request->product_id, 'user_id' => Auth::id(), 'quantity' => $request->quantity, 'address' => $request->address ]); return response()->json([ 'status' => (bool) $order, 'data' => $order, 'message' => $order ? 'Order Created!' : 'Error Creating Order' ]); } public function show(Order $order) { return response()->json($order,200); } public function update(Request $request, Order $order) { $status = $order->update( $request->only(['quantity']) ); return response()->json([ 'status' => $status, 'message' => $status ? 'Order Updated!' : 'Error Updating Order' ]); } public function destroy(Order $order) { $status = $order->delete(); return response()->json([ 'status' => $status, 'message' => $status ? 'Order Deleted!' : 'Error Deleting Order' ]); } }
در OrderController بالا ما شش متد داریم:
این متدها برای کنترلرهای ما است. ما کنترلر را با توجه به مشخصاتی که در قسمت اول ارائه کردیم ایجاد کرده ایم. کار بعدی که باید انجام دهیم این است که مسیرهای API خود را تعریف کنیم.
اکنون که همه درخواستهایی را که میخواستیم، بهطور کامل تعریف کردیم، اجازه دهید APIهای ایجاد این درخواستها را ایجاد کنیم. فایل routes/api.php
را باز کنید و محتوای زیر را جایگزین کنید:
<?php use Illuminate\Http\Request; Route::post('login', 'UserController@login'); Route::post('register', 'UserController@register'); Route::get('/products', 'ProductController@index'); Route::post('/upload-file', 'ProductController@uploadFile'); Route::get('/products/{product}', 'ProductController@show'); Route::group(['middleware' => 'auth:api'], function(){ Route::get('/users','UserController@index'); Route::get('users/{user}','UserController@show'); Route::patch('users/{user}','UserController@update'); Route::get('users/{user}/orders','UserController@showOrders'); Route::patch('products/{product}/units/add','ProductController@updateUnits'); Route::patch('orders/{order}/deliver','OrderController@deliverOrder'); Route::resource('/orders', 'OrderController'); Route::resource('/products', 'ProductController')->except(['index','show']); });
قرار دادن تعریف مسیرها در فایل routes/api.php
به لاراول می گوید که آنها مسیرهای API هستند، بنابراین لاراول مسیرها را با یک پیشوند api/ در URL ایجاد می کند تا آنها را از مسیرهای وب متمایز کند.
افزودن میانافزار auth:api
تضمین میکند که هر فراخوانی مسیرهای آن گروه باید احراز هویت شود.
نکته ای که باید به آن توجه داشت این است که استفاده از متد resource در کلاس Route به ما کمک می کند تا مسیرهای اضافی را بدون نیاز به ایجاد آنها ایجاد کنیم. در مورد کنترلرهای resource و routes اینجا را بخوانید.
برای مشاهده لیست کامل routes، دستور زیر را اجرا کنید:
php artisan route:list
از آنجایی که قسمت فرانت اند این اپلیکیشن را با Vue خواهیم ساخت، باید مسیرهای وب را برای آن تعریف کنیم. فایل routes/web.php را باز کنید و محتویات آن را با موارد زیر جایگزین کنید:
<?php Route::get('/{any}', function(){ return view('landing'); })->where('any', '.*');
این کد هر درخواست وب را به یک api هدایت می کند، که ورودی برنامه Vue شما خواهد بود.
Vue یک فریم ورک پیشرفته برای ساخت رابط های کاربری است. برخلاف سایر فریم ورکهای یکپارچه، Vue از ابتدا به گونهای طراحی شده است که به صورت تدریجی قابل استفاده و پذیرش در برنامه باشد / vuejs.org
لاراول با Vue سازگار است، بنابراین تنها کاری که برای دریافت Vue باید انجام دهیم نصب پکیج های noed است. دستور زیر را اجرا کنید:
npm install
در مرحله بعد، ما به VueRouter نیاز داریم تا مسیریابی بین اجزای مختلف برنامه Vue را مدیریت کند. برای نصب VueRouter دستور زیر را اجرا کنید:
npm install vue-router
در مرحله بعد، بیایید فایل ویو لندینگ را بسازیم که برنامه Vue ما را نصب می کند. فایل source/views/landing.blade.php
را ایجاد کنید و کد زیر را به آن اضافه کنید:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{csrf_token()}}"> <title>Big Store</title> <link href=" {{ mix('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <app></app> </div> <script src="{{ mix('js/bootstrap.js') }}"></script> <script src="{{ mix('js/app.js') }}"></script> </body> </html>
در کد بالا، HTML برنامه خود را داریم. اگر دقت کنید، می توانید تگ برنامه را ببینید. این نقطه ورود به برنامه Vue ما و جایی است که کامپوننت ها دانلود می شوند.
از آنجایی که ما از app.js
برای راه اندازی VueRouter خود استفاده خواهیم کرد، هنوز باید Bootstrap و Axios را کامپایل کنیم. وارد (import) کردن Bootstrap و Axios در فایل bootstrap.js
است، بنابراین باید آن را کامپایل کنیم.
فایل webpack.mix.js
را تغییر دهید تا همه asset ها را کامپایل کند:
[...] mix.js('resources/assets/js/app.js', 'public/js') .js('resources/assets/js/bootstrap.js', 'public/js') .sass('resources/assets/sass/app.scss', 'public/css');
فایل webpack.mix.js
فایلهای پیکربندی laravel-mix را در خود نگه میدارد رپری (wrapper) در اطراف Webpack ایجاد میکند. به ما امکان می دهد از توانایی های شگفت انگیز گردآوری asset وب پک بدون نیاز به نوشتن تنظیمات Webpack توسط خودمان استفاده کنیم. در اینجا می توانید درباره Webpack اطلاعات بیشتری کسب کنید.
صفحه اصلی برنامه Vue را آماده کنید. یک فایل جدید، source/assets/js/views/Home.vue
ایجاد کنید و کد زیر را به فایل اضافه کنید:
<template> <div> <div class="container-fluid hero-section d-flex align-content-center justify-content-center flex-wrap ml-auto"> <h2 class="title">Welcome to the bigStore</h2> </div> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="row"> <div class="col-md-4 product-box" v-for="(product,index) in products" @key="index"> <router-link :to="{ path: '/products/'+product.id}"> <img :src="product.image" :alt="product.name"> <h5><span v-html="product.name"></span> <span class="small-text text-muted float-right">$ {{product.price}}</span> </h5> <button class="col-md-4 btn btn-sm btn-primary float-right">Buy Now</button> </router-link> </div> </div> </div> </div> </div> </div> </template> <script> export default { data(){ return { products : [] } }, mounted(){ axios.get("api/products/").then(response => this.products = response.data) } } </script>
در بالا تگ باز و بسته تمپلیت HTML خود، کد کامپوننت Vue خود را داریم. در آنجا محتویات محصولات را رندر می کنیم و برای هر محصول تصویر، نام، آیدی، قیمت و یونیت های موجود را نمایش می دهیم. ما از ویژگی v-html برای رندر HTML خام استفاده می کنیم که استفاده از کاراکترهای خاص در نام محصول را برای ما آسان می کند.
در تگ اسکریپت، data() را تعریف کردیم و تمام متغیرهایی را که می توانیم در تمپلیت خود استفاده کنیم را در خود جای می دهد. همچنین متد ()mounted را تعریف کردیم که پس از بارگذاری کامپوننت فراخوانی می شود. در این متد mount شده، ما محصولات خود را از API بارگذاری می کنیم و سپس متغیر products را طوری مقداردهی می کنیم که تمپلیت ما با داده های API به روز شود.
در همین فایل کد زیر را به اضافه کنید:
<style scoped> .small-text { font-size: 14px; } .product-box { border: 1px solid #cccccc; padding: 10px 15px; } .hero-section { height: 30vh; background: #ababab; align-items: center; margin-bottom: 20px; margin-top: -20px; } .title { font-size: 60px; color: #ffffff; } </style>
در کد بالا، استایلی را برای استفاده در کامپوننت Welcome تعریف کرده ایم.
طبق مستندات Vue داریم:
هنگامی که یک تگ <style> دارای ویژگی scoped باشد، CSS آن فقط برای المنت های کامپوننت فعلی اعمال می شود. این شبیه به کپسولهسازی استایل موجود در Shadow DOM است. با برخی اخطارها همراه است اما نیازی به pollyfill ها ندارد.
سپس یک فایل دیگر، به نام resources/assets/js/views/App.vue
ایجاد کنید. این فایل کانتینر برنامه خواهد بود که در آن همه کامپوننت های دیگر بارگذاری می شوند. در این فایل کد زیر را اضافه کنید:
<template> <div> <nav class="navbar navbar-expand-md navbar-light navbar-laravel"> <div class="container"> <router-link :to="{name: 'home'}" class="navbar-brand">Big Store</router-link> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Left Side Of Navbar --> <ul class="navbar-nav mr-auto"></ul> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <router-link :to="{ name: 'login' }" class="nav-link" v-if="!isLoggedIn">Login</router-link> <router-link :to="{ name: 'register' }" class="nav-link" v-if="!isLoggedIn">Register</router-link> <span v-if="isLoggedIn"> <router-link :to="{ name: 'userboard' }" class="nav-link" v-if="user_type == 0"> Hi, {{name}}</router-link> <router-link :to="{ name: 'admin' }" class="nav-link" v-if="user_type == 1"> Hi, {{name}}</router-link> </span> <li class="nav-link" v-if="isLoggedIn" @click="logout"> Logout</li> </ul> </div> </div> </nav> <main class="py-4"> <router-view @loggedIn="change"></router-view> </main> </div> </template>
در تمپلیت Vue در بالا، از برخی تگهای خاص Vue مانند router-link استفاده کردیم که به ما کمک میکند لینکهایی را برای مسیریابی صفحات تعریفشده در روتر خود ایجاد کنیم. ما همچنین router-view را داریم، جایی که تمام صفحات کامپوننت فرزند بارگذاری می شوند.
در ادامه، زیر تگ تمپلیت بسته شده، کد زیر را اضافه کنید:
<script> export default { data() { return { name: null, user_type: 0, isLoggedIn: localStorage.getItem('bigStore.jwt') != null } }, mounted() { this.setDefaults() }, methods : { setDefaults() { if (this.isLoggedIn) { let user = JSON.parse(localStorage.getItem('bigStore.user')) this.name = user.name this.user_type = user.is_admin } }, change() { this.isLoggedIn = localStorage.getItem('bigStore.jwt') != null this.setDefaults() }, logout(){ localStorage.removeItem('bigStore.jwt') localStorage.removeItem('bigStore.user') this.change() this.$router.push('/') } } } </script>
در تعریف اسکریپت، ویژگیهای متدها را داریم و در آنجا سه متد تعریف شده داریم:
در کامپوننت router-view خود، ما رویداد loggedIn را بررسی میکنیم که متد change را فراخوانی می کند. این رویداد هر زمان که وارد سیستم میشویم توسط کامپوننت ما فعال میشود. این راهی است که به کامپوننت برنامه میگوییم وقتی کاربر وارد سیستم میشود، خودش را بهروزرسانی کند.
سپس فایل های زیر را در دایرکتوری resources/assets/js/views
ایجاد کنید:
این فایلها همه صفحاتی را که bigStore خواهند داشت، در خود جای میدهند. آنها باید قبل از راه اندازی VueRouter ایجاد شوند تا خطایی ایجاد نکند. برای راهاندازی مسیریابی برای برنامه تک صفحهای Vue، فایل source/assets/js/app.j
s خود را باز کنید و محتوای آن را با کد زیر جایگزین کنید:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) import App from './views/App' import Home from './views/Home' import Login from './views/Login' import Register from './views/Register' import SingleProduct from './views/SingleProduct' import Checkout from './views/Checkout' import Confirmation from './views/Confirmation' import UserBoard from './views/UserBoard' import Admin from './views/Admin' const router = new VueRouter({ mode: 'history', routes: [ { path: '/', name: 'home', component: Home }, { path: '/login', name: 'login', component: Login }, { path: '/register', name: 'register', component: Register }, { path: '/products/:id', name: 'single-products', component: SingleProduct }, { path: '/confirmation', name: 'confirmation', component: Confirmation }, { path: '/checkout', name: 'checkout', component: Checkout, props: (route) => ({ pid: route.query.pid }) }, { path: '/dashboard', name: 'userboard', component: UserBoard, meta: { requiresAuth: true, is_user: true } }, { path: '/admin/:page', name: 'admin-pages', component: Admin, meta: { requiresAuth: true, is_admin: true } }, { path: '/admin', name: 'admin', component: Admin, meta: { requiresAuth: true, is_admin: true } }, ], }) router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { if (localStorage.getItem('bigStore.jwt') == null) { next({ path: '/login', params: { nextUrl: to.fullPath } }) } else { let user = JSON.parse(localStorage.getItem('bigStore.user')) if (to.matched.some(record => record.meta.is_admin)) { if (user.is_admin == 1) { next() } else { next({ name: 'userboard' }) } } else if (to.matched.some(record => record.meta.is_user)) { if (user.is_admin == 0) { next() } else { next({ name: 'admin' }) } } next() } } else { next() } })
در بالا، ما VueRouter را import کرده ایم و آن را به برنامه Vue خود اضافه کرده ایم. ما مسیرهایی را برای برنامه خود تعریف کردیم و سپس آن را در نمونه Vue ثبت کردیم تا برای همه اجزای Vue در دسترس باشد.
هر یک از اشیا route دارای یک نام است که از آن برای شناسایی و فراخوانی آن مسیر استفاده می کنیم. همچنین دارای یک مسیر است که می توانید به طور مستقیم در مرورگر خود از آن بازدید کنید. در نهایت یک کامپوننت دارد که هنگام بازدید از مسیر نصب می شود.
در برخی از مسیرها، meta را تعریف کردیم که شامل متغیرهایی است که میخواهیم هنگام دسترسی به مسیر بررسی کنیم. در این مورد، ما در حال بررسی هستیم که آیا مسیر نیاز به احراز هویت دارد یا خیر و آیا فقط به ادمین ها یا کاربران عادی محدود می شود یا نه.
ما میان افزار (middleware) beforeEach را برای روتر استفاده کرده ایم که هر مسیر را قبل از کار کردن بررسی می کند. متد این متغیرها را می گیرد:
ما از beforeEach برای بررسی مسیرهایی که نیاز به احراز هویت دارند قبل از اینکه بتوانید به آنها دسترسی داشته باشید استفاده می کنیم. برای آن مسیرها، بررسی می کنیم که آیا کاربر احراز هویت شده است یا خیر. اگر کاربر نیست، او را به صفحه ورود می فرستیم. اگر کاربر احراز هویت شده باشد، بررسی می کنیم که آیا مسیر به کاربران ادمین یا کاربران عادی محدود شده است. ما هر کاربر را بر اساس سطح دسترسی آنها به مکان مناسب هدایت می کنیم.
حالا خطوط زیر را به انتهای فایل app.js اضافه کنید:
const app = new Vue({ el: '#app', components: { App }, router, });
این فایل باعث می شود که یک راه اندازی برای Vue انجام شود. در این نمونه global، ما کامپوننت App را فقط به این دلیل نصب میکنیم که VueRouter برای جابجایی بین سایر کامپوننتها به آن نیاز دارد.
اکنون، ما آماده هستیم تا سایر ویوها را برای برنامه خود ایجاد کنیم.
در قسمت قبل، تمام فایل های view را برای برنامه Vue خود ایجاد کردیم، البته به همه آنها محتوا اضافه نکردیم. بیایید با صفحات login و register شروع کنیم.
فایل source/assets/js/views/Login.vue
را باز کرده و در کد زیر قرار دهید:
<template> <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card card-default"> <div class="card-header">Login</div> <div class="card-body"> <form> <div class="form-group row"> <label for="email" class="col-sm-4 col-form-label text-md-right">E-Mail Address</label> <div class="col-md-6"> <input id="email" type="email" class="form-control" v-model="email" required autofocus> </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">Password</label> <div class="col-md-6"> <input id="password" type="password" class="form-control" v-model="password" required> </div> </div> <div class="form-group row mb-0"> <div class="col-md-8 offset-md-4"> <button type="submit" class="btn btn-primary" @click="handleSubmit"> Login </button> </div> </div> </form> </div> </div> </div> </div> </div> </template>
در بالا یک فرم داریم که هنوز عملکرد زیادی ندارد، بنابراین بیایید کد زیر را به همان فایل اضافه کنیم تا مقداری اسکریپت Vue اضافه کنیم:
<script> export default { data() { return { email: "", password: "" } }, methods: { handleSubmit(e) { e.preventDefault() if (this.password.length > 0) { let email = this.email let password = this.password axios.post('api/login', {email, password}).then(response => { let user = response.data.user let is_admin = user.is_admin localStorage.setItem('bigStore.user', JSON.stringify(user)) localStorage.setItem('bigStore.jwt', response.data.token) if (localStorage.getItem('bigStore.jwt') != null) { this.$emit('loggedIn') if (this.$route.params.nextUrl != null) { this.$router.push(this.$route.params.nextUrl) } else { this.$router.push((is_admin == 1 ? 'admin' : 'dashboard')) } } }); } } } } </script>
در بالا یک متد handleSubmit داریم که هنگام ارسال فرم فعال می شود. در این متد سعی می کنیم با استفاده از API احراز هویت کنیم. اگر احراز هویت موفقیت آمیز باشد، access token و دادههای کاربر را در localStorage ذخیره میکنیم تا بتوانیم به آنها در سراسر برنامه خود دسترسی داشته باشیم.
ما همچنین یک رویداد loggedIn را منتشر می کنیم تا کامپوننت پدر نیز بتواند به روز شود. در آخر بررسی می کنیم که آیا کاربر از صفحه دیگری به صفحه ورود ارسال شده است یا خیر، سپس کاربر را به آن صفحه می فرستیم. اگر کاربر به طور مستقیم وارد سیستم شده باشد، نوع کاربر را بررسی کرده و کاربر را به درستی هدایت می کنیم.
در مرحله بعد، فایل source/assets/js/views/Register.vue
را باز کرده و در قسمت زیر قرار دهید:
<template> <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card card-default"> <div class="card-header">Register</div> <div class="card-body"> <form> <div class="form-group row"> <label for="name" class="col-md-4 col-form-label text-md-right">Name</label> <div class="col-md-6"> <input id="name" type="text" class="form-control" v-model="name" required autofocus> </div> </div> <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">E-Mail Address</label> <div class="col-md-6"> <input id="email" type="email" class="form-control" v-model="email" required> </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">Password</label> <div class="col-md-6"> <input id="password" type="password" class="form-control" v-model="password" required> </div> </div> <div class="form-group row"> <label for="password-confirm" class="col-md-4 col-form-label text-md-right">Confirm Password</label> <div class="col-md-6"> <input id="password-confirm" type="password" class="form-control" v-model="password_confirmation" required> </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary" @click="handleSubmit"> Register </button> </div> </div> </form> </div> </div> </div> </div> </div> </template>
در بالا HTML فرم ثبت نام را داریم. بیایید اسکریپت کامپوننت را در زیر تگ بسته تمپلیت اضافه کنیم:
<script> export default { props : ['nextUrl'], data(){ return { name : "", email : "", password : "", password_confirmation : "" } }, methods : { handleSubmit(e) { e.preventDefault() if (this.password !== this.password_confirmation || this.password.length <= 0) { this.password = "" this.password_confirmation = "" return alert('Passwords do not match') } let name = this.name let email = this.email let password = this.password let c_password = this.password_confirmation axios.post('api/register', {name, email, password, c_password}).then(response => { let data = response.data localStorage.setItem('bigStore.user', JSON.stringify(data.user)) localStorage.setItem('bigStore.jwt', data.token) if (localStorage.getItem('bigStore.jwt') != null) { this.$emit('loggedIn') let nextUrl = this.$route.params.nextUrl this.$router.push((nextUrl != null ? nextUrl : '/')) } }) } } } </script>
کامپوننت register به طور مشابه با کامپوننت login عمل می کند. ما دادههای کاربر را برای احراز هویت به API ارسال میکنیم و در صورت دریافت پاسخ مطلوب، token و user کاربر را در localStorage ذخیره میکنیم.
ما قبلا صفحه اصلی را در فصل آخر تعریف کرده بودیم و لیستی از محصولات موجود را برگردانده بودیم. اکنون، ما میخواهیم تمام صفحات فروشگاه دیگر را بسازیم.
فایل source/assets/js/views/SingleProduct.vue
را باز کرده و در کد زیر قرار دهید:
<template> <div class="container"> <div class="row"> <div class="col-md-8 offset-md-2"> <img :src="product.image" :alt="product.name"> <h3 class="title" v-html="product.name"></h3> <p class="text-muted">{{product.description}}</p> <h4> <span class="small-text text-muted float-left">$ {{product.price}}</span> <span class="small-text float-right">Available Quantity: {{product.units}}</span> </h4> <br> <hr> <router-link :to="{ path: '/checkout?pid='+product.id }" class="col-md-4 btn btn-sm btn-primary float-right">Buy Now</router-link> </div> </div> </div> </template> <script> export default { data(){ return { product : [] } }, beforeMount(){ let url = `/api/products/${this.$route.params.id}` axios.get(url).then(response => this.product = response.data) } } </script> <style scoped> .small-text { font-size: 18px; } .title { font-size: 36px; } </style>
در بالا محصول را به عنوان یک attribute داده داریم که از آن برای نمایش اطلاعات در صفحه مانند فایل Home.vue استفاده می کنیم. در اسکریپت اجزای متد Vue's beforeMount را تعریف کردیم و اطلاعات محصول را در آنجا واکشی کردیم. beforeMount قبل از رندر شدن کامپوننت فراخوانی می شود، بنابراین داده های لازم را برای رندر کردن کامپوننت واکشی می کند. اگر پس از نصب کامپوننت، داده ها را دریافت کنیم، قبل از به روز رسانی کامپوننت با خطا مواجه خواهیم شد.
سپس فایل source/assets/js/views/Checkout.vue
را باز کرده و کد زیر را برای تمپلیت و استایل HTML قرار دهید:
<template> <div class="container"> <div class="row"> <div class="col-md-8 offset-md-2"> <div class="order-box"> <img :src="product.image" :alt="product.name"> <h2 class="title" v-html="product.name"></h2> <p class="small-text text-muted float-left">$ {{product.price}}</p> <p class="small-text text-muted float-right">Available Units: {{product.units}}</p> <br> <hr> <label class="row"><span class="col-md-2 float-left">Quantity: </span><input type="number" name="units" min="1" :max="product.units" class="col-md-2 float-left" v-model="quantity" @change="checkUnits"></label> </div> <br> <div> <div v-if="!isLoggedIn"> <h2>You need to login to continue</h2> <button class="col-md-4 btn btn-primary float-left" @click="login">Login</button> <button class="col-md-4 btn btn-danger float-right" @click="register">Create an account</button> </div> <div v-if="isLoggedIn"> <div class="row"> <label for="address" class="col-md-3 col-form-label">Delivery Address</label> <div class="col-md-9"> <input id="address" type="text" class="form-control" v-model="address" required> </div> </div> <br> <button class="col-md-4 btn btn-sm btn-success float-right" v-if="isLoggedIn" @click="placeOrder">Continue</button> </div> </div> </div> </div> </div> </template> <style scoped> .small-text { font-size: 18px; } .order-box { border: 1px solid #cccccc; padding: 10px 15px; } .title { font-size: 36px; } </style>
در زیر آن موارد زیر را در اسکریپت پیست کنید:
<script> export default { props : ['pid'], data(){ return { address : "", quantity : 1, isLoggedIn : null, product : [] } }, mounted() { this.isLoggedIn = localStorage.getItem('bigStore.jwt') != null }, beforeMount() { axios.get(`/api/products/${this.pid}`).then(response => this.product = response.data) if (localStorage.getItem('bigStore.jwt') != null) { this.user = JSON.parse(localStorage.getItem('bigStore.user')) axios.defaults.headers.common['Content-Type'] = 'application/json' axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('bigStore.jwt') } }, methods : { login() { this.$router.push({name: 'login', params: {nextUrl: this.$route.fullPath}}) }, register() { this.$router.push({name: 'register', params: {nextUrl: this.$route.fullPath}}) }, placeOrder(e) { e.preventDefault() let address = this.address let product_id = this.product.id let quantity = this.quantity axios.post('api/orders/', {address, quantity, product_id}) .then(response => this.$router.push('/confirmation')) }, checkUnits(e){ if (this.quantity > this.product.units) { this.quantity = this.product.units } } } } </script>
در بالا متد beforeMount را تعریف کردیم که در آن اطلاعات محصول را واکشی می کنیم. سپس ما متد mounted را داریم که در آن وضعیت احراز هویت را بررسی می کنیم. در پراپرتی متد ها، متد checkUnits را تعریف کردیم که بررسی میکند کاربر میخواهد چند سفارش داشته باشد و سپس متد placeOrder را تعریف میکنیم که سفارش را انجام میدهد. سپس فایل source/assets/js/views/Confirmation.vue
را باز کرده و کد زیر را در آن قرار دهید:
<template> <div> <div class="container-fluid hero-section d-flex align-content-center justify-content-center flex-wrap ml-auto"> <h2> <span class="title"><strong>Thank You!</strong></span><br> <span class="medium-text">Your order has been placed.</span><br> <router-link :to="{name: 'userboard'}" class="small-link"> See your orders </router-link> </h2> </div> </div> </template> <script> export default {} </script> <style scoped> .medium-text { font-size: 36px; } .small-link { font-size: 24px; text-decoration: underline; color: #777; } .product-box { border: 1px solid #cccccc; padding: 10px 15px; } .hero-section { height: 80vh; align-items: center; margin-top: -20px; margin-bottom: 20px; } .title { font-size: 60px; } </style>
این کامپوننت یک پیام تشکر ساده را نمایش میدهد و لینکی را برای کاربران فراهم میکند تا به داشبورد خود بروند تا سفارشهایی را که انجام دادهاند و وضعیت این سفارشها را مشاهده کنند. الان بیایید داشبورد کاربر را ایجاد کنیم.
داشبورد کاربر جایی است که کاربران می توانند تمام سفارشات خود را ببینند. فایل source/assets/js/views/UserBoard.vue
را باز کرده و کد زیر را برای الگو و استایل قرار دهید:
<template> <div> <div class="container-fluid hero-section d-flex align-content-center justify-content-center flex-wrap ml-auto"> <h2 class="title">All your orders</h2> </div> <div class="container"> <div class="row"> <div class="col-md-12"> <br> <div class="row"> <div class="col-md-4 product-box" v-for="(order,index) in orders" @key="index"> <img :src="order.product.image" :alt="order.product.name"> <h5><span v-html="order.product.name"></span><br> <span class="small-text text-muted">$ {{order.product.price}}</span> </h5> <hr> <span class="small-text text-muted">Quantity: {{order.quantity}} <span class="float-right">{{order.is_delivered == 1? "shipped!" : "not shipped"}}</span> </span> <br><br> <p><strong>Delivery address:</strong> <br>{{order.address}}</p> </div> </div> </div> </div> </div> </div> </template> <style scoped> .small-text { font-size: 14px; } .product-box { border: 1px solid #cccccc; padding: 10px 15px; } .hero-section { background: #ababab; height: 20vh; align-items: center; margin-bottom: 20px; margin-top: -20px; } .title { font-size: 60px; color: #ffffff; } </style>
سپس برای script کد زیر را قرار دهید:
<script> export default { data() { return { user : null, orders : [] } }, beforeMount() { this.user = JSON.parse(localStorage.getItem('bigStore.user')) axios.defaults.headers.common['Content-Type'] = 'application/json' axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('bigStore.jwt') axios.get(`api/users/${this.user.id}/orders`) .then(response => this.orders = response.data) } } </script>
در کد بالا، تمام سفارشهای کاربر را قبل از نصب کامپوننت دریافت میکنیم، سپس آنها را حلقه زده و در صفحه نمایش میدهیم.
داشبورد ادمین جایی است که محصولات جدید اضافه میشوند، محصولات موجود ویرایش میشوند و سفارشها به صورت تحویل داده شده تنظیم میشوند.
برای ادمین، ما از چهار کامپوننت مختلف استفاده خواهیم کرد که آنها را بر اساس آدرس اینترنتی که کاربر به آن دسترسی دارد، پیاده سازی می کنیم. بیایید آن را در عمل ببینیم. فایل source/assets/js/views/Admin.vue
را باز کرده و کد زیر را در آن قرار دهید:
<template> <div> <div class="container-fluid hero-section d-flex align-content-center justify-content-center flex-wrap ml-auto"> <h2 class="title">Admin Dashboard</h2> </div> <div class="container"> <div class="row"> <div class="col-md-3"> <ul style="list-style-type:none"> <li class="active"><button class="btn" @click="setComponent('main')">Dashboard</button></li> <li><button class="btn" @click="setComponent('orders')">Orders</button></li> <li><button class="btn" @click="setComponent('products')">Products</button></li> <li><button class="btn" @click="setComponent('users')">Users</button></li> </ul> </div> <div class="col-md-9"> <component :is="activeComponent"></component> </div> </div> </div> </div> </template> <script> import Main from '../components/admin/Main' import Users from '../components/admin/Users' import Products from '../components/admin/Products' import Orders from '../components/admin/Orders' export default { data() { return { user: null, activeComponent: null } }, components: { Main, Users, Products, Orders }, beforeMount() { this.setComponent(this.$route.params.page) this.user = JSON.parse(localStorage.getItem('bigStore. d fuser')) axios.defaults.headers.common['Content-Type'] = 'application/json' axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('bigStore.jwt') }, methods: { setComponent(value) { switch(value) { case "users": this.activeComponent = Users this.$router.push({name: 'admin-pages', params: {page: 'users'}}) break; case "orders": this.activeComponent = Orders this.$router.push({name: 'admin-pages', params: {page: 'orders'}}) break; case "products": this.activeComponent = Products this.$router.push({name: 'admin-pages', params: {page: 'products'}}) break; default: this.activeComponent = Main this.$router.push({name: 'admin'}) break; } } } } </script> <style scoped> .hero-section { height: 20vh; background: #ababab; align-items: center; margin-bottom: 20px; margin-top: -20px; } .title { font-size: 60px; color: #ffffff; } </style>
در کد بالا چهار کامپوننت را وارد و استفاده می کنیم ولی هنوز آنها را ایجاد نکرده ایم. آنها به عنوان کامپوننت در داخل کامپوننت والد Admin.vue استفاده خواهند شد.
در تمپلیت خود، مسیریابی را برای جابجایی بین کامپوننت ها تعریف کردیم. هر لینک متد setComponent
را فراخوانی می کند و سپس مقداری را به آن ارسال می کند. متد setComponent
فقط کامپوننت را با استفاده از دستور switch
مقداردهی می کند.
بیایید اولین کامپوننت را برای کامپوننت Admin ایجاد کنیم. فایل Main.vue
را در دایرکتوری resources/assets/js/components/admin
ایجاد کنید و موارد زیر را در آن قرار دهید:
<template> <div class="row"> <div class="col-md-4 product-box d-flex align-content-center justify-content-center flex-wrap big-text"> <a href='/admin/orders'>Orders ({{orders.length}})</a> </div> <hr> <div class="col-md-4 product-box d-flex align-content-center justify-content-center flex-wrap big-text"> <a href='/admin/products'>Products ({{products.length}})</a> </div> <div class="col-md-4 product-box d-flex align-content-center justify-content-center flex-wrap big-text"> <a href='/admin/users'>Users ({{users.length}})</a> </div> </div> </template> <script> export default { data() { return { user : null, orders : [], products : [], users : [] } }, mounted() { axios.get('/api/users/').then(response => this.users = response.data) axios.get('/api/products/').then(response => this.products = response.data) axios.get('/api/orders/').then(response => this.orders = response.data) } } </script> <style scoped> .big-text { font-size: 28px; } .product-box { border: 1px solid #cccccc; padding: 10px 15px; height: 20vh } </style>
در کد بالا ما API های users ،orders و products را فراخوانی میکنیم و دادههای آنها را برمیگردانیم. فایل Orders.vue
را در source/assets/js/components/admin
ایجاد کنید و موارد زیر را در آن قرار دهید:
<template> <div> <table class="table table-responsive table-striped"> <thead> <tr> <td></td> <td>Product</td> <td>Quantity</td> <td>Cost</td> <td>Delivery Address</td> <td>is Delivered?</td> <td>Action</td> </tr> </thead> <tbody> <tr v-for="(order,index) in orders" @key="index"> <td>{{index+1}}</td> <td v-html="order.product.name"></td> <td>{{order.quantity}}</td> <td>{{order.quantity * order.product.price}}</td> <td>{{order.address}}</td> <td>{{order.is_delivered == 1? "Yes" : "No"}}</td> <td v-if="order.is_delivered == 0"><button class="btn btn-success" @click="deliver(index)">Deliver</button></td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { orders : [] } }, beforeMount(){ axios.get('/api/orders/').then(response => this.orders = response.data) }, methods: { deliver(index) { let order = this.orders[index] axios.patch(`/api/orders/${order.id}/deliver`).then(response => { this.orders[index].is_delivered = 1 this.$forceUpdate() }) } } } </script>
در beforeMount
ما تمام سفارشهایی را که قبل از رندر شدن کامپوننت انجام میشوند واکشی میکنیم. هنگامی که دکمه Deliver کلیک می شود، روش تحویل فعال می شود. ما برای تحویل سفارشات API ها را فراخوانی می کنیم. برای اینکه تغییر فورا در صفحه منعکس شود، [$forceUpdate]
(https://vuejs.org/v2/api/#vm-forceUpdate) را فراخوانی می کنیم.
فایل Users.vue را در source/assets/js/components/admin ایجاد کرده و کد زیر را در آن قرار دهید:
<template> <div> <table class="table table-responsive table-striped"> <thead> <tr> <td></td> <td>Name</td> <td>Email</td> <td>Joined</td> <td>Total Orders</td> </tr> </thead> <tbody> <tr v-for="(user,index) in users" @key="index"> <td>{{index+1}}</td> <td>{{user.name}}</td> <td>{{user.email}}</td> <td>{{user.created_at}}</td> <td>{{user.orders.length}}</td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { users : [] } }, beforeMount() { axios.get('/api/users/').then(response => this.users = response.data) } } </script>
در بالا ما تمام داده های user را واکشی می کنیم و سپس آن را در صفحه نمایش می دهیم. در مرحله بعد، فایل Products.vue
را در source/assets/js/components/admin
ایجاد کنید و کد تمپلیت زیر را در آن قرار دهید:
<template> <div> <table class="table table-responsive table-striped"> <thead> <tr> <td></td> <td>Product</td> <td>Units</td> <td>Price</td> <td>Description</td> </tr> </thead> <tbody> <tr v-for="(product,index) in products" @key="index" @dblclick="editingItem = product"> <td>{{index+1}}</td> <td v-html="product.name"></td> <td v-model="product.units">{{product.units}}</td> <td v-model="product.price">{{product.price}}</td> <td v-model="product.price">{{product.description}}</td> </tr> </tbody> </table> <modal @close="endEditing" :product="editingItem" v-show="editingItem != null"></modal> <modal @close="addProduct" :product="addingProduct" v-show="addingProduct != null"></modal> <br> <button class="btn btn-primary" @click="newProduct">Add New Product</button> </div> </template>
در زیر آن کد اسکریپت زیر را قرار دهید:
<script> import Modal from './ProductModal' export default { data() { return { products: [], editingItem: null, addingProduct: null } }, components: {Modal}, beforeMount() { axios.get('/api/products/').then(response => this.products = response.data) }, methods: { newProduct() { this.addingProduct = { name: null, units: null, price: null, image: null, description: null, } }, endEditing(product) { this.editingItem = null let index = this.products.indexOf(product) let name = product.name let units = product.units let price = product.price let description = product.description axios.put(`/api/products/${product.id}`, {name, units, price, description}) .then(response => this.products[index] = product) }, addProduct(product) { this.addingProduct = null let name = product.name let units = product.units let price = product.price let description = product.description let image = product.image axios.post("/api/products/", {name, units, price, description, image}) .then(response => this.products.push(product)) } } } </script>
در پراپرتی متد ها متدهای زیر را تعریف کردیم:
ما یک کامپوننت ProductModal را import کردیم و در ادامه آن را ایجاد خواهیم کرد. modal برای ویرایش یک محصول موجود یا ایجاد یک محصول جدید استفاده خواهد شد. با دوبار کلیک کردن روی محصول، حالت ویرایش محصول فعال می شود.
فایل ProductModal.vue
را در source/assets/js/components/admin
ایجاد کرده و کد زیر را برای استایل آن قرار دهید:
<template> <div class="modal-mask"> <div class="modal-wrapper"> <div class="modal-container"> <div class="modal-header"> <slot name="header" v-html="data.name"></slot> </div> <div class="modal-body"> <slot name="body"> Name: <input type="text" v-model="data.name"> Units: <input type="text" v-model="data.units"> Price: <input type="text" v-model="data.price"> <textarea v-model="data.description" placeholder="description"></textarea> <span > <img :src="data.image" v-show="data.image != null"> <input type="file" id="file" @change="attachFile"> </span> </slot> </div> <div class="modal-footer"> <slot name="footer"> <button class="modal-default-button" @click="uploadFile"> Finish </button> </slot> </div> </div> </div> </div> </template> <style scoped> .modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5); display: table; transition: opacity .3s ease; } .modal-wrapper { display: table-cell; vertical-align: middle; } .modal-container { width: 300px; margin: 0px auto; padding: 20px 30px; background-color: #fff; border-radius: 2px; box-shadow: 0 2px 8px rgba(0, 0, 0, .33); transition: all .3s ease; font-family: Helvetica, Arial, sans-serif; } .modal-header h3 { margin-top: 0; color: #42b983; } .modal-body { margin: 20px 0; } .modal-default-button { float: right; } .modal-enter { opacity: 0; } .modal-leave-active { opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { -webkit-transform: scale(1.1); transform: scale(1.1); } </style>
سپس موارد زیر را بعد از تگ بسته شدن style قرار دهید:
<script> export default { props: ['product'], data() { return { attachment: null } }, computed: { data: function() { if (this.product != null) { return this.product } return { name: "", units: "", price: "", description: "", image: false } } }, methods: { attachFile(event) { this.attachment = event.target.files[0]; }, uploadFile(event) { if (this.attachment != null) { var formData = new FormData(); formData.append("image", this.attachment) let headers = {'Content-Type': 'multipart/form-data'} axios.post("/api/upload-file", formData, {headers}).then(response => { this.product.image = response.data this.$emit('close', this.product) }) } else { this.$emit('close', this.product) } } } } </script>
هنگامی که modal داده های یک محصول را دریافت می کند، هر فیلد را از قبل با داده ها پر می کند. وقتی تصویری را ضمیمه میکنیم و فرم modal را ارسال میکنیم، آپلود میشود و URL مربوط به تصویر به ما بازگردانده میشود.
attribute تصویر محصول را با url به روز می کنیم، سپس یک رویداد close منتشر می کنیم و محصول را با آن برمی گردانیم. اگر هیچ تصویری ضمیمه نشده باشد، یک رویداد close منتشر میکنیم و دادههای محصول را به همراه آن برمیگردانیم. این کامپوننت modal نمونه ای است که در مستندات Vue در این آدرس آموزش داده شده است.
گزینه های پرداخت زیادی برای پلتفرم های تجارت الکترونیک وجود دارد. شما با توجه به به نیاز خود و آنچه در کشور شما موجود است انتخاب می کنید. بسیاری از پردازشگرهای پرداخت محبوب مانند Stripe دارای راهنمای عالی برای ادغام در یک برنامه جاوا اسکریپت یا PHP هستند که می توانید از آنها استفاده کنید. اگرچه این آموزش آن را پوشش نمی دهد، می توانید آن را به عنوان تمرینی برای تمرین در نظر بگیرید.
ساخت اپلیکیشن خود را به پایان رساندیم. کار بعدی این است که برنامه Vue خود را کامپایل کرده و بک اند Laravel خود را ارائه دهیم. دستور ساخت اپلیکیشن را اجرا کنید.
npm run prod
سپس، دستور را اجرا کنید تا برنامه را آماده استفاده شود:
php artisan serve
در این آموزش از Laravel و Vue برای ساخت یک اپلیکیشن فروشگاه ساده استفاده کردیم. این راهنما یک پیاده سازی فروشگاه را ارائه می دهد و می تواند شروعی برای یک تحول در زمینه ساخت فروشگاه اینترنتی با لاراول و Vue باشد.
ما در ابتدا سمت بک اند (API) فروشگاه اینترنتی را آم
اده کردیم و سپس با استفاده از Vue سمت فرانتد آن را به اجرا درآوردیم. ابزارهای مختلفی برای راه اندازی این فروشگاه استفاده شده که به برقراری ارتباط بین فرانتد و بک اند کمک می کند. همچنین کد کامل برنامه در GitHub موجود است.
امیدوارم این آموزش مورد توجه شما قرار گرفته باشد.
منبع: وب سایت Pusher
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.