Xây dựng ứng dụng CRUD sử dụng Vue.js trong Laravel (ok)

https://allaravel.com/blog/xay-dung-ung-dung-crud-su-dung-vue-js-trong-laravel/

php artisan make:migration create_products_table --create=products
Created Migration: 2017_04_24_024728_create_products_table

C:\xampp\htdocs\songkhoe\routes\web.php

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\VueJSProductController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
    return view('welcome');
});
Route::get('/vue-product', [VueJSProductController::class,'showVueProduct']);
Route::resource('vueproduct',VueJSProductController::class);

C:\xampp\htdocs\songkhoe\public\js\product.js

Vue.http.headers.common['X-CSRF-TOKEN'] = $("#token").attr("value");
new Vue({
    el: '#manage-vue',
    data: {
        items: [],
        pagination: {
            total: 0,
            per_page: 2,
            from: 1,
            to: 0,
            current_page: 1
        },
        offset: 4,
        formErrors:{},
        formErrorsUpdate:{},
        newItem : {'name':'','price':'','content':''},
        fillItem : {'name':'','price':'','content':'','id':''}
    },
    computed: {
        isActived: function () {
            return this.pagination.current_page;
        },
        pagesNumber: function () {
            if (!this.pagination.to) {
                return [];
            }
            var from = this.pagination.current_page - this.offset;
            if (from < 1) {
                from = 1;
            }
            var to = from + (this.offset * 2);
            if (to >= this.pagination.last_page) {
                to = this.pagination.last_page;
            }
            var pagesArray = [];
            while (from <= to) {
                pagesArray.push(from);
                from++;
            }
            return pagesArray;
        }
    },
    ready : function(){
        this.getvueproduct(this.pagination.current_page);
    },
    methods : {
        getvueproduct: function(page){
            this.$http.get('/vueproduct?page='+page).then((response) => {
                if(response.status == 200 && response.statusText == 'OK') {
                    let dataParse = JSON.parse(response.data);
                    this.$set('items', dataParse.data.data);
                    this.$set('pagination', dataParse.pagination);
                }
            });
        },
        createItem: function(){
            var input = this.newItem;
            this.$http.post('/vueproduct',input).then((response) => {
                this.changePage(this.pagination.current_page);
                this.newItem = {'name':'','price':'','content':''};
                $("#create-item").modal('hide');
                toastr.success('Item Created Successfully.', 'Success Alert', {timeOut: 5000});
            }, (response) => {
                this.formErrors = response.data;
            });
        },
        deleteItem: function(item){
            this.$http.delete('/vueproduct/'+item.id).then((response) => {
                this.changePage(this.pagination.current_page);
                toastr.success('Item Deleted Successfully.', 'Success Alert', {timeOut: 5000});
            });
        },
        editItem: function(item){
            this.fillItem.name = item.name;
            this.fillItem.id = item.id;
            this.fillItem.price = item.price;
            this.fillItem.content = item.content;
            $("#edit-item").modal('show');
        },
        updateItem: function(id){
            var input = this.fillItem;
            this.$http.put('/vueproduct/'+id,input).then((response) => {
                this.changePage(this.pagination.current_page);
                this.fillItem = {'name':'','price':'','content':'','id':''};
                $("#edit-item").modal('hide');
                toastr.success('Item Updated Successfully.', 'Success Alert', {timeOut: 5000});
            }, (response) => {
                this.formErrorsUpdate = response.data;
            });
        },
        changePage: function (page) {
            this.pagination.current_page = page;
            this.getvueproduct(page);
        }
    }
});

C:\xampp\htdocs\songkhoe\app\Http\Controllers\VueJSProductController.php

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
class VueJSProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $products = Product::latest()->paginate(5);
        $response = [
            'pagination' => [
                'total'        => $products->total(),
                'per_page'     => $products->perPage(),
                'current_page' => $products->currentPage(),
                'last_page'    => $products->lastPage(),
                'from'         => $products->firstItem(),
                'to'           => $products->lastItem()
            ],
            'data' => $products
        ];
        return response()->json($response);
    }
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'name'    => 'required',
            'price'   => 'required',
            'content' => 'required',
        ]);
        $create = Product::create($request->all());
        return response()->json($create);
    }
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }
    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $this->validate($request, [
            'name'    => 'required',
            'price'   => 'required',
            'content' => 'required',
        ]);
        $edit = Product::find($id)->update($request->all());
        return response()->json($edit);
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        Product::find($id)->delete();
        return response()->json(['done']);
    }
    public function showVueProduct(){
        return view('frontend.vue-product');
    }
}

C:\xampp\htdocs\songkhoe\app\Models\Product.php

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
    use HasFactory;
    protected $fillable = [
        "name",
        "price",
        "content"
    ];
}

C:\xampp\htdocs\songkhoe\resources\views\frontend\vue-product.blade.php

@extends('layouts.default')
@section('title', 'Referral system - Allaravel.com')
@section('link-header')
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet">
    <meta id="token" name="token" value="{{ csrf_token() }}">
@endsection
@section('content')
    <div class="container" id="manage-vue">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h2>Laravel + VueJS Product CRUD</h2>
                </div>
                <div class="pull-right">
                    <button type="button" class="btn btn-success" data-toggle="modal" data-target="#create-item">
                        Tạo sản phẩm mới
                    </button>
                </div>
            </div>
        </div>
        <!-- Item Listing -->
        <div class="row">
            <div class="col-md-12">
                <table class="table table-bordered">
                    <tr>
                        <th class="col-md-2">Name</th>
                        <th class="col-md-1">Price</th>
                        <th class="col-md-7">Content</th>
                        <th class="col-md-2">Action</th>
                    </tr>
                    <tr v-for="item in items">
                        <td>@{{ item.name }}</td>
                        <td>@{{ item.price }}</td>
                        <td>@{{ item.content }}</td>
                        <td>
                            <button class="btn btn-primary" @click.prevent="editItem(item)">Edit</button>
                            <button class="btn btn-danger" @click.prevent="deleteItem(item)">Delete</button>
                        </td>
                    </tr>
                </table>
                <!-- Pagination -->
                <nav>
                    <ul class="pagination">
                        <li v-if="pagination.current_page > 1">
                            <a href="#" aria-label="Previous"
                               @click.prevent="changePage(pagination.current_page - 1)">
                                <span aria-hidden="true">«</span>
                            </a>
                        </li>
                        <li v-for="page in pagesNumber"
                            v-bind:class="[ page == isActived ? 'active' : '']">
                            <a href="#"
                               @click.prevent="changePage(page)">@{{ page }}</a>
                        </li>
                        <li v-if="pagination.current_page < pagination.last_page">
                            <a href="#" aria-label="Next"
                               @click.prevent="changePage(pagination.current_page + 1)">
                                <span aria-hidden="true">»</span>
                            </a>
                        </li>
                    </ul>
                </nav>
            </div>
        </div>
        <!-- Create Item Modal -->
        <div class="modal fade" id="create-item" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
                        <h4 class="modal-title" id="myModalLabel">Tạo sản phẩm mới</h4>
                    </div>
                    <div class="modal-body">
                        <form method="POST" enctype="multipart/form-data" v-on:submit.prevent="createItem">
                            <div class="form-group">
                                <label for="name">Name:</label>
                                <input type="text" name="name" class="form-control" v-model="newItem.name" />
                                <span v-if="formErrors['name']" class="error text-danger">@{{ formErrors['name'] }}</span>
                            </div>
                            <div class="form-group">
                                <label for="price">Price:</label>
                                <input type="text" name="price" class="form-control" v-model="newItem.price" />
                                <span v-if="formErrors['price']" class="error text-danger">@{{ formErrors['price'] }}</span>
                            </div>
                            <div class="form-group">
                                <label for="content">Content:</label>
                                <textarea name="content" class="form-control" v-model="newItem.content"></textarea>
                                <span v-if="formErrors['content']" class="error text-danger">@{{ formErrors['content'] }}</span>
                            </div>
                            <div class="form-group">
                                <button type="submit" class="btn btn-success">Tạo sản phẩm</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <!-- Edit Item Modal -->
        <div class="modal fade" id="edit-item" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
                        <h4 class="modal-title" id="myModalLabel">Chỉnh sửa sản phẩm</h4>
                    </div>
                    <div class="modal-body">
                        <form method="POST" enctype="multipart/form-data" v-on:submit.prevent="updateItem(fillItem.id)">
                            <div class="form-group">
                                <label for="name">Name:</label>
                                <input type="text" name="name" class="form-control" v-model="fillItem.name" />
                                <span v-if="formErrorsUpdate['name']" class="error text-danger">@{{ formErrorsUpdate['name'] }}</span>
                            </div>
                            <div class="form-group">
                                <label for="price">Price:</label>
                                <input type="text" name="price" class="form-control" v-model="fillItem.price" />
                                <span v-if="formErrorsUpdate['price']" class="error text-danger">@{{ formErrorsUpdate['price'] }}</span>
                            </div>
                            <div class="form-group">
                                <label for="content">Content:</label>
                                <textarea name="content" class="form-control" v-model="fillItem.content"></textarea>
                                <span v-if="formErrorsUpdate['content']" class="error text-danger">@{{ formErrorsUpdate['content'] }}</span>
                            </div>
                            <div class="form-group">
                                <button type="submit" class="btn btn-success">Cập nhật sản phẩm</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
@section('js')
    <script type="text/javascript" src="//code.jquery.com/jquery.js"></script>
    <script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
    <script type="text/javascript" src="//cdn.jsdelivr.net/vue.resource/0.9.3/vue-resource.min.js"></script>
    <script type="text/javascript" src="{{ Asset('js/product.js') }}"></script>
@endsection

C:\xampp\htdocs\songkhoe\resources\views\layouts\default.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    @yield('link-header')
</head>
<body>
    @yield('content')
    @yield('js')
</body>
</html>

Last updated