+36

Bài 17: Phân quyền với Laravel và VueJS

Mình đã cập nhật lại tất cả các bài với các thay đổi ở hiện tại ở năm 2024: Vue 3, Vite, Laravel 11x,...

Cập nhật gần nhất: 09/06/2024

Chào mừng các bạn quay trở lại với series học Laravel với VueJS của mình, ở bài này mình sẽ hướng dẫn các bạn các phân quyền bằng Laravel và VueJS mà không cần cài đặt thêm bất kì package hay library nào khác.

Ở bài này ta sẽ tiếp nối bài trước gọi Laravel với axios từ VueJS, và ta sẽ phát triển thêm chút, chỉ cho phép Admin được thêm sửa xoá, còn user thường thì chỉ được xem danh sách sản phẩm thôi.

Trong bài hôm nay ta sẽ sử dụng Laravel Gate để check phần phân quyền nhé, ta sẽ làm nó ở mức độ cơ bản để mọi người cung hiểu được nhé. Lên thuyền thôi nào 🚀🚀

Setup

Ở bước setup này mình sẽ làm qua thật nhanh các bước setup để ta có kết quả như bài trước, sau đó ta tập trung vào phần phân quyền nhé. Nếu có gì thắc mắc ở bước setup các bạn giúp mình xem lại bài trước hoặc comment ở bài này cũng được nha

Laravel

Đầu tiên ta tạo project mới:

composer create-project laravel/laravel laravel-rbac-simple

# hoặc
laravel new laravel-rbac-simple

Khi được hỏi thì ta cứ chọn mặc định nhé:

Screenshot 2024-06-09 at 6.22.06 PM.png

Screenshot 2024-06-09 at 6.22.32 PM.png

Tiếp đó ta cài phần setup cho VueJS nha:

composer require laravel/ui
php artisan ui vue --auth
npm install

Sau đó các bạn update lại database ở .env cho đúng với của các bạn nha, ví dụ của mình:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=laravel
DB_USERNAME=myuser
DB_PASSWORD=myuserpass

Nhớ tạo database tên là laravel trước nhé để tí ta còn migrate

Sau đó ta tạo model Product và migration cho nó:

php artisan make:model Product -m

Sau đó chúng ta vào database/migrations/create_products_table.php và sửa lại hàm up() như sau:

public function up(): void
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->double('price');
        $table->timestamps();
    });
}

Tiếp theo ta vào database/migrations/create_users_table.php và sửa lại hàm up() như sau:

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->boolean('is_admin')->default(false); // Thêm trường này
    $table->timestamps();
});

Ở trên ta thêm trường is_admin để đánh dấu user này có phải admin hay không

Tiếp đó ở database/seeders/DatabaseSeeder.php và sửa lại hàm run() như sau:

public function run(): void
{
    // User::factory(10)->create();

    User::factory()->create([
        'name' => 'Admin User',
        'email' => 'admin@example.com',
        'password' => bcrypt('12345678'),
        'is_admin' => true,
    ]);
}

Ở đây tí nữa ta sẽ tạo sẵn một admin user vào DB lúc migrate

Bây giờ ta chạy migration nhé:

php artisan migrate --seed

Thấy in ra như sau là ổn rồi 😎:

Screenshot 2024-06-09 at 9.34.36 PM.png

Tiếp theo chúng ta vào app/Models/Product.php và sửa lại như sau

...

class Product extends Model
{
    use HasFactory;

    protected $fillable = [
    	'name', 'price'
    ];
}

Ở bài này ta sẽ dùng Laravel Resource Controller cho tiện nha, các bạn chạy command sau:

php artisan make:controller ProductController --resource

Sau đó các bạn mở file routes/web.php và sửa lại như sau:

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
})->middleware('auth');

Auth::routes();

Route::resource('products', App\Http\Controllers\ProductController::class)->middleware('auth');

Tiếp đó các bạn tạo cho mình file resources/views/products.blade.php:

@extends('layouts.app')

Rất đơn giản không có gì mấy ở file này 😂😂

Ở file resources/views/layouts/app.blade.php, các bạn sửa lại như sau:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">

    <!-- Scripts -->
    @guest
        @vite(['resources/sass/app.scss'])
    @else
        @vite(['resources/sass/app.scss', 'resources/js/app.js'])
    @endguest
</head>
<body>
    <div>
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-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 me-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ms-auto">
                        <!-- Authentication Links -->
                        @guest
                            @if (Route::has('login'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                                </li>
                            @endif

                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4" id="app">
            @yield('content')
        </main>
    </div>
</body>
</html>

Ở trên mình thêm vào đoạn @vite(['resources/sass/app.scss'])<head>, lí do là vì tí nữa ta sẽ mount app VueJS từ file app.js, nếu ta không sửa thì tí nữa trang Login/Register cũng sẽ hiển thị component của VueJS

Tiếp tục ở file app/Http/Controllers/Auth/LoginController.phpRegisterController.php ta sửa như sau để sau khi login/register thành công nó sẽ trả về trang chủ nhé:

protected $redirectTo = '/';

Cuối cùng là ta update lại file app/Http/Controllers/ProductController.php như bài trước:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return Product::orderBy('created_at', 'desc')->paginate(5);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|min:5',
            'price' => 'required|numeric|gt:0',
        ]);
        $product = Product::create([
            'name'     => $request->input('name'),
            'price'    => $request->input('price'),
        ]);
        return response([
            'product' => $product
        ], 200);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        $validated = $request->validate([
            'name' => 'required|min:5',
            'price' => 'required|numeric|gt:0',
        ]);
    
        $product = Product::find($id);
    
        $product->name = $request->input('name');
        $product->price = $request->input('price');
        
        $product->save();
    
        return response([
            'product' => $product
        ], 200);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        $product = Product::find($id);
        $product->delete();
        return response([
            'result' => 'success'
        ], 200);
    }
}

Vue

Tiếp theo tới phần setup cho VueJS

Các bạn tạo cho mình file resources/js/components/ProductList.vue:

<template>
  <div class="api-calling container mt-5">
    <h1>Create Product</h1>
    <transition name="fade">
      <div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="error">
        <b>{{ error.message }}</b>
        <ul>
          <li v-for="(errorName, index) in error.errors" :key="index">
            {{ errorName[0] }}
          </li>
        </ul>
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"
          @click="error = null"></button>
      </div>
    </transition>
    <div class="form-group">
      <label>Name</label>
      <input v-model="product.name" type="text" class="form-control" placeholder="Name...">
    </div>
    <div class="form-group">
      <label>Price</label>
      <input v-model="product.price" type="text" class="form-control" placeholder="Price...">
    </div>
    <button class="btn btn-primary mt-2" @click="createProduct">Create</button>

    <hr>
    <h1>List Products</h1>
    <table class="table">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">Name</th>
          <th scope="col">Price</th>
          <th scope="col">Actions</th>
        </tr>
      </thead>
      <transition-group name="list" tag="tbody">
        <tr v-for="(product, index) in listProducts.data" :key="product.id">
          <th scope="row">{{ product.id }}</th>
          <td v-if="!product.isEdit">
            {{ product.name }}
          </td>
          <td v-else>
            <input type="text" v-model="selectedProduct.name" class="form-control">
          </td>
          <td v-if="!product.isEdit">
            {{ product.price }}
          </td>
          <td v-else>
            <input type="text" v-model="selectedProduct.price" class="form-control">
          </td>
          <td v-if="!product.isEdit">
            <button class="btn btn-primary" @click="selectProduct(product)">Edit</button>
            <button class="btn btn-danger ms-2" @click="deleteProduct(product, index)">Delete</button>
          </td>
          <!-- ... -->
          <td v-else>
            <button class="btn btn-primary" @click="updateProduct(index)">Save</button>
            <button class="btn btn-danger ms-2" @click="product.isEdit = false">Cancel</button>
          </td>
        </tr>
      </transition-group>
    </table>
    <div>
      {{ listProducts.from }} - {{ listProducts.to }} of {{ listProducts.total }}
    </div>
    <ul class="pagination">
      <li class="page-item" :class="{ 'disabled': listProducts.prev_page_url === null }"
        @click="listProducts.prev_page_url && getListProducts(listProducts.current_page - 1)">
        <a class="page-link" href="#">Previous</a>
      </li>
      <li class="page-item" v-if="listProducts.prev_page_url" @click="getListProducts(listProducts.current_page - 1)">
        <a class="page-link" href="#">{{ listProducts.current_page - 1 }}</a>
      </li>
      <li class="page-item active">
        <a class="page-link" href="#">{{ listProducts.current_page }}</a>
      </li>
      <li class="page-item" v-if="listProducts.next_page_url" @click="getListProducts(listProducts.current_page + 1)">
        <a class="page-link" href="#">{{ listProducts.current_page + 1 }}</a>
      </li>
      <li class="page-item" :class="{ 'disabled': listProducts.next_page_url === null }"
        @click="listProducts.next_page_url && getListProducts(listProducts.current_page + 1)">
        <a class="page-link" href="#">Next</a>
      </li>
    </ul>
  </div>
</template>

<script setup>

import axios from 'axios'
import { onBeforeMount, ref } from 'vue';

const product = ref({
  name: '',
  price: 0
})

const listProducts = ref({})
const error = ref(null)
const selectedProduct = ref(null)

onBeforeMount(() => {
  getListProducts()
})

const createProduct = async () => {
  try {
    error.value = null
    const response = await axios.post('/products', {
      name: product.value.name,
      price: product.value.price
    })
    listProducts.value.data.unshift({
      ...response.data.product,
      isEdit: false
    })

    // reset giá trị form về ban đầu
    product.value = {
      name: '',
      price: 0
    }
  } catch (e) {
    error.value = e.response.data
  }
}

const getListProducts = async (page = 1) => {
  try {
    const response = await axios.get('/products?page=' + page)
    listProducts.value = response.data
    listProducts.value.data.forEach(item => {
      item.isEdit = false
    })
  } catch (e) {
    error.value = e.response.data
  }
}

const selectProduct = (product) => {
  product.isEdit = true
  selectedProduct.value = { ...product }
}

const updateProduct = async (index) => {
  try {
    const response = await axios.put('/products/' + selectedProduct.value.id, {
      name: selectedProduct.value.name,
      price: selectedProduct.value.price
    })

    listProducts.value.data[index].name = response.data.product.name
    listProducts.value.data[index].price = response.data.product.price
    listProducts.value.data[index].isEdit = false
  } catch (e) {
    error.value = e.response.data
  }
}

const deleteProduct = async (product, index) => {
  try {
    await axios.delete('/products/' + product.id)
    listProducts.value.data.splice(index, 1)
  } catch (e) {
    error.value = e.response.data
  }
}
</script>

<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

// ------ THÊM VÀO ĐOẠN BÊN DƯỚI
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

Ở file resources/js/app.js ta sửa như sau:

import './bootstrap';
import { createApp } from 'vue';
import ProductList from './components/ProductList.vue';

const app = createApp(ProductList);

app.mount('#app');

Cuối cùng là ta start project lên thôi, ta chạy 2 command sau mỗi cái ở 1 terminal nhé:

php artisan serve

npm run dev

Sau đó truy cập từ trình duyệt ở địa chỉ http://localhost:8000, thấy lên trang Login là ngon rồi:

Screenshot 2024-06-09 at 10.05.19 PM.png

Login với account admin@example.com/12345678 như khi nãy ta tạo ở Seeder sau đó ta sẽ vào được app nha:

Screenshot 2024-06-09 at 10.03.35 PM.png

Ở bước này các bạn chạy lại các thao tác thêm sửa xoá xem oke không nhé

Phân quyền

Ở bài này ta sẽ dùng Laravel Gate để làm phân quyền nhé

Đầu tiên ở file app/Providers/AppServiceProvider.php ta tạo các quyền ta muốn:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Gate::define('create-products', function (User $user) {
            return $user->is_admin;
        });

        Gate::define('update-products', function (User $user) {
            return $user->is_admin;
        });
        
        Gate::define('delete-products', function (User $user) {
            return $user->is_admin;
        });
    }
}

Ở trên ta có 3 quyền là create-products/update-products/delete-products, cả 3 quyền đó đều yêu cầu user phải là admin is_admin=true

Sau đó ta sửa lại app/Http/Controllers/ProductController.php như sau:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\Gate;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        return Product::orderBy('created_at', 'desc')->paginate(5);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        if (!Gate::allows('create-products')) {
            return response()->json(['message' => 'Forbidden'], 403);
        }

        $validated = $request->validate([
            'name' => 'required|min:5',
            'price' => 'required|numeric|gt:0',
        ]);
        $product = Product::create([
            'name'     => $request->input('name'),
            'price'    => $request->input('price'),
        ]);
        return response([
            'product' => $product
        ], 200);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        if (!Gate::allows('update-products')) {
            return response()->json(['message' => 'Forbidden'], 403);
        }
        $validated = $request->validate([
            'name' => 'required|min:5',
            'price' => 'required|numeric|gt:0',
        ]);
    
        $product = Product::find($id);
    
        $product->name = $request->input('name');
        $product->price = $request->input('price');
        
        $product->save();
    
        return response([
            'product' => $product
        ], 200);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        if (!Gate::allows('delete-products')) {
            return response()->json(['message' => 'Forbidden'], 403);
        }

        $product = Product::find($id);
        $product->delete();
        return response([
            'result' => 'success'
        ], 200);
    }
}

Ở trên các bạn để ý rằng, ở mỗi method trong controller ta đều có bước đầu tiên là check quyền, dùng Gate::allows('...') để check xem user hiện tại có quyền tương ứng hay không, nếu có thì mới thực hiện tiếp, còn không thì trả về 403

Giờ nếu ta quay lại trình duyệt, Logout ra ngoài, sau đó tạo 1 tài khoản bất kì, mặc định is_admin=false (user thường), sau đó vào trong app Vue và thực hiện bất kì thao tác thêm sửa xoá nào sẽ thấy báo lỗi:

Screenshot 2024-06-09 at 10.19.56 PM.png

Bước tiếp theo là với user thường thì ta cần ẩn hết form tạo product, các nút Edit/Delete đi, để làm điều đó thì ta cần phải lấy được thông tin user hiện tại và truyền vào phía app VueJS để nó biết, có 2 cách:

  1. gọi API để lấy thông tin user
  2. dùng trực tiếp auth user của Laravel

Rõ ràng ta nên chọn cách số 2, không cần phải gọi API, ngay lập tức khi app Vue load lên thì user đã có ở đó rồi, vì Laravel render file blade, file blade load app.js và tạo app Vue, mà ở file blade là ta có thể lấy được thông tin user rồi

Giờ ta sửa lại file resources/views/products.blade.php như sau nhé:

@extends('layouts.app')

@section('content')
<script>
  window.__user__ = @json($user);
</script>
@endsection

Tiếp đó ta sửa lại file routes/web.php như sau:

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    $user = auth()->user();
    return view('products', compact('user')); // -> ở đây
})->middleware('auth');

Auth::routes();

Route::resource('products', App\Http\Controllers\ProductController::class)->middleware('auth');

ở trên các bạn thấy, ở file route, thì ta đã lấy ra auth()->user() và return nó về cùng với view, ở file blade thì ta serialize nó thành json và gán nó vào biến global __user__

Giờ việc của ta đơn giản là vào ProductList.vue lấy ra user lưu lại thôi:

<script setup>
    ...
    const user = ref(window.__user__)
</script>

Sau đó F5 chạy app lên, mở Vue Devtool ta sẽ thấy thông tin user ở đó:

Screenshot 2024-06-09 at 10.29.02 PM.png

Giờ ta chỉ cần ẩn hết những phần UI để nếu user không phải admin thì sẽ không xem được, source code đầy đủ như sau:

<template>
  <div class="api-calling container mt-5">
    <template v-if="user.is_admin">
      <h1>Create Product</h1>
      <transition name="fade">
        <div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="error">
          <b>{{ error.message }}</b>
          <ul>
            <li v-for="(errorName, index) in error.errors" :key="index">
              {{ errorName[0] }}
            </li>
          </ul>
          <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"
            @click="error = null"></button>
        </div>
      </transition>
      <div class="form-group">
        <label>Name</label>
        <input v-model="product.name" type="text" class="form-control" placeholder="Name...">
      </div>
      <div class="form-group">
        <label>Price</label>
        <input v-model="product.price" type="text" class="form-control" placeholder="Price...">
      </div>
      <button class="btn btn-primary mt-2" @click="createProduct">Create</button>

      <hr>
    </template>
    <h1>List Products</h1>
    <table class="table">
      <thead>
        <tr>
          <th scope="col">ID</th>
          <th scope="col">Name</th>
          <th scope="col">Price</th>
          <th scope="col" v-if="user.is_admin">Actions</th>
        </tr>
      </thead>
      <transition-group name="list" tag="tbody">
        <tr v-for="(product, index) in listProducts.data" :key="product.id">
          <th scope="row">{{ product.id }}</th>
          <td v-if="!product.isEdit">
            {{ product.name }}
          </td>
          <td v-else>
            <input type="text" v-model="selectedProduct.name" class="form-control">
          </td>
          <td v-if="!product.isEdit">
            {{ product.price }}
          </td>
          <td v-else>
            <input type="text" v-model="selectedProduct.price" class="form-control">
          </td>
          <template v-if="user.is_admin">
            <td v-if="!product.isEdit">
              <button class="btn btn-primary" @click="selectProduct(product)">Edit</button>
              <button class="btn btn-danger ms-2" @click="deleteProduct(product, index)">Delete</button>
            </td>
            <!-- ... -->
            <td v-else>
              <button class="btn btn-primary" @click="updateProduct(index)">Save</button>
              <button class="btn btn-danger ms-2" @click="product.isEdit = false">Cancel</button>
            </td>
          </template>
        </tr>
      </transition-group>
    </table>
    <div>
      {{ listProducts.from }} - {{ listProducts.to }} of {{ listProducts.total }}
    </div>
    <ul class="pagination">
      <li class="page-item" :class="{ 'disabled': listProducts.prev_page_url === null }"
        @click="listProducts.prev_page_url && getListProducts(listProducts.current_page - 1)">
        <a class="page-link" href="#">Previous</a>
      </li>
      <li class="page-item" v-if="listProducts.prev_page_url" @click="getListProducts(listProducts.current_page - 1)">
        <a class="page-link" href="#">{{ listProducts.current_page - 1 }}</a>
      </li>
      <li class="page-item active">
        <a class="page-link" href="#">{{ listProducts.current_page }}</a>
      </li>
      <li class="page-item" v-if="listProducts.next_page_url" @click="getListProducts(listProducts.current_page + 1)">
        <a class="page-link" href="#">{{ listProducts.current_page + 1 }}</a>
      </li>
      <li class="page-item" :class="{ 'disabled': listProducts.next_page_url === null }"
        @click="listProducts.next_page_url && getListProducts(listProducts.current_page + 1)">
        <a class="page-link" href="#">Next</a>
      </li>
    </ul>
  </div>
</template>

<script setup>

import axios from 'axios'
import { onBeforeMount, ref } from 'vue';

const product = ref({
  name: '',
  price: 0
})

const listProducts = ref({})
const error = ref(null)
const selectedProduct = ref(null)
const user = ref(window.__user__)

onBeforeMount(() => {
  getListProducts()
})

const createProduct = async () => {
  try {
    error.value = null
    const response = await axios.post('/products', {
      name: product.value.name,
      price: product.value.price
    })
    listProducts.value.data.unshift({
      ...response.data.product,
      isEdit: false
    })

    // reset giá trị form về ban đầu
    product.value = {
      name: '',
      price: 0
    }
  } catch (e) {
    error.value = e.response.data
  }
}

const getListProducts = async (page = 1) => {
  try {
    const response = await axios.get('/products?page=' + page)
    listProducts.value = response.data
    listProducts.value.data.forEach(item => {
      item.isEdit = false
    })
  } catch (e) {
    error.value = e.response.data
  }
}

const selectProduct = (product) => {
  product.isEdit = true
  selectedProduct.value = { ...product }
}

const updateProduct = async (index) => {
  try {
    const response = await axios.put('/products/' + selectedProduct.value.id, {
      name: selectedProduct.value.name,
      price: selectedProduct.value.price
    })

    listProducts.value.data[index].name = response.data.product.name
    listProducts.value.data[index].price = response.data.product.price
    listProducts.value.data[index].isEdit = false
  } catch (e) {
    error.value = e.response.data
  }
}

const deleteProduct = async (product, index) => {
  try {
    await axios.delete('/products/' + product.id)
    listProducts.value.data.splice(index, 1)
  } catch (e) {
    error.value = e.response.data
  }
}
</script>

<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

// ------ THÊM VÀO ĐOẠN BÊN DƯỚI
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

Ở trên nếu các bạn để ý thì mình có gom nhóm các phần UI mà chỉ admin truy cập được và đặt vào <template> sau đó thêm v-if="user.is_admin" cho nó là xong

Kết quả cuối cùng cho ta như sau:

Screenshot 2024-06-09 at 10.32.19 PM.png

Kết luận

Vậy là ta đã hoàn thành bài về phân quyền với Laravel và VueJS rồi, mặc dù phần logics trong bài này cũng khá là dễ, nhưng hi vọng nó đã cho các bạn được ý tưởng cách áp dụng vào thực tế như thế nào

Ở một bài khác mình sẽ hướng dẫn các bạn cách sử dụng Laravel Passport dùng để authenticate ở mức chi tiết và mạnh mẽ hơn nhé.

Cám ơn các bạn đã theo dõi, có gì thắc mắc để lại dưới comment cho mình nhé ^^!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí