+20

LaraVue - Xây dựng ứng dụng CRUD với Laravel và Vuejs (Phần I)

Mình sẽ đi thẳng vào vấn đề, bài viết này (có thể sẽ là một seri) mình muốn mang đến một ứng dụng nho nhỏ nhằm kết hợp Laravel và VueJs (cả hai đều là framework) để xây dựng "một" trang ứng dụng đơn giản với các chức năng thân thuộc CRUD giúp quản lý một cuốn sách dạy nấu ăn (nói đến ăn uống cho dễ tiêu ). Chữ "một" mà mình nói ở đây hàm ý với sự kết hợp đó chúng ta tạo ra được một ứng dụng dạng single-page.

Những thứ buộc phải có

  • Tất nhiên là clone laravel về máy (mình sử dụng phiên bản 5.4).
  • Không thể thiếu VueJs.
  • Thư viện Vue Router (cái này giống route của laravel).
  • Axios (Hiểu nôm na nó tạo ra request và gửi tới laravel, ví dụ chúng ta tạo một tài khoản đăng nhập thì sẽ gửi một POST request đến cho laravel xử lý, và lúc này chúng ta dùng axios, như thế cái này na ná như dùng POST của ajax, nhưng xịn hơn 😃))

Frame Vue và 2 thư viện kia chúng ta khai báo trong package.json và cài đặt thông qua npm

"dependencies": {
    "axios": "^0.15.3",
    "vue": "^2.2.2",
    "vue-router": "^2.3.0"
}

Xây dựng model

Mình khái quát về database thế này nhé, trang mình đang làm là quản lý một cuốn sách dậy nấu ăn, vì thế các model chúng ta cần là "Recipe", "RecipeDirection", "RecipeIngredient" (Công thức - Bước làm - Nguyên liệu). Một công thức gồm nhiều nguyên liệu và nhiều bước làm. Chi tiết về các thuộc tính cũng như mối quan hệ mình thể hiện ở hình dưới đây: Và hẳn là mọi người cũng sẽ viết migration cho các model trên nhé!

Xây dựng chức năng show list công thức

Sản phẩm của chúng ta trông sẽ như sau:

Bắt tay vào controller

Vì chúng ta dùng vue để xây dựng front-end, chính vì thế controller chúng ta sẽ trả về dữ liệu json để vue hứng và render ra page. Khá đơn giản thôi:

public function index()
{
    $recipes = Recipe::orderBy('created_at', 'desc')
        ->get(['id', 'name', 'image']);

    return response()
        ->json([
            'recipes' => $recipes
        ]);
}

Xây dựng trang blade <welcome.blade.php>

<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link rel="stylesheet" type="text/css" href="{{ mix('css/app.css') }}">
    </head>
    <body>
        <div id="root">
            Chính tại đây vue sẽ tìm đến và render page cho chúng ta !!!
        </div>
    </body>
    <script type="text/javascript" src="{{ mix('js/app.js') }}"></script>
</html>

File app.js

import Vue from 'vue'

import App from './App.vue'
import router from './router'

const app = new Vue({
	el: '#root',
	template: `<app></app>`,
	components: { App },
	router
})

Ở đây chúng ta import Vue trong node_module, còn App và router tại file App.vue và .router/index.js

  • Nhìn vào đây chúng ta thấy Vue sẽ tìm đến chỗ có #root để render các component vào trong đó.
  • Rồi giờ chúng ta quan tâm tới hai thành phần là components và router kia.

1. Components

  • Đại khái thế này, khi ứng dụng của chúng ta chạy, nó sẽ chạy đến route của laravel và gọi tới view welcome.blade:
<?php

Route::get('/{any}', function () {
    return view('welcome');
})->where(['any' => '.*']);
  • View welcome sẽ chạy tới app.js, sau đó nó sẽ tìm tới chỗ có thành phần #root để render cái compoents vào đó. Quy trình được diễn tả như sau: Và chúng ta đã render ra được cái compoents App chính là cái thanh nav trên cùng kìa 😃)))

Từ đây các bạn có thể thấy rằng chúng ta có thể chia ra thành nhiều components và import vào trong view để dễ quản lý, nó chả khác gì việc các bạn chia nhỏ trang html của mình ra thành các "header", "content" và "footer" cả.

2. Router

Đây, chính phần này đây sử dụng thư viện vue-router để mapping giữa route của laravel (url hiện tại) và view tương ứng để render vào trong phần <router-view></router-view>. Để ý lại chút, chúng ta import cái "router" chính là từ file index.js trong thư mục assets/js/router/index.js và bên trong nó trông như vầy:

import Vue from 'vue'
import VueRouter from 'vue-router'

import RecipeIndex from '../views/Recipe/Index.vue'
Vue.use(VueRouter)

const router = new VueRouter({
	mode: 'history',
	routes: [
		{ path: '/', component: RecipeIndex },
	]
})
console.log(router)

export default router

Cái thằng vue-router (chính là index.js đó) kia nó render ra các path và compoents tương ứng. Như tại trường hợp này ứng với url: http://127.0.0.1:8000/ thì nó trả về compoent: RecipeIndex và nó sẽ render về compoents đó vào <router-view></router-view> trong App.vue. File ../views/Recipe/Index.vue trông như thế này:

<template>
	<div class="recipe__list">
		<div class="recipe__item" v-for="recipe in recipes">
			<router-link class="recipe__inner" :to="`/recipes/${recipe.id}`">
				<img :src="`/images/${recipe.image}`" v-if="recipe.image">
				<p class="recipe__name">{{recipe.name}}</p>
			</router-link>
		</div>
	</div>
</template>
<script type="text/javascript">
	import { get } from '../../helpers/api'
	export default {
		data() {
			return {
				recipes: []
			}
		},
		created() {
			get('/api/recipes')
				.then((res) => {
					this.recipes = res.data.recipes
				})
		}
	}
</script>

Để ý vào phần code <script></script> ta thấy Index.vue sẽ gọi đến action index trong RecipeController và trả về data recipes, sau đó chính trên recipe__list sẽ dùng v-for (của vue) để load ra list. Để làm được điều đó chúng ta đã sử dụng cái thư viện axios đã nói ở trên, nó giúp vue gọi đến được API của laravel và nhận dữ liệu trả về để render cho chính nó trong vòng for. Cùng xem file api.js có gì nào:

import axios from 'axios'
import Auth from '../store/auth'
export function get(url) {
    return axios({
    	method: 'GET',
    	url: url,
    	headers: {
    		'Authorization': `Bearer ${Auth.state.api_token}`
    	}
    })
}

export function post(url, payload) {
    return axios({
    	method: 'POST',
    	url: url,
    	data: payload,
    	headers: {
    		'Authorization': `Bearer ${Auth.state.api_token}`
    	}
    })
}
// delete is reserved keyword
export function del(url) {
    return axios({
        method: 'DELETE',
        url: url,
        headers: {
            'Authorization': `Bearer ${Auth.state.api_token}`
        }
    })
}

export function interceptors(cb) {
    axios.interceptors.response.use((res) => {
        return res;
    }, (err) => {
        cb(err)
        return Promise.reject(err)
    })
}

Giao diện của trang index sẽ như sau

Điểm lại quá trình chạy

1- Ứng dụng chạy vào route index và trả về view welcome.blade 2- View welcome chạy và load app.js 3- Trong app.js nó sẽ tìm 2 compoent AppIndex (được trả về thông qua router) để render vào phần #root được định nghĩa trong welcome.blade 4- Trong Index.vue chúng ta gọi đến function index trong RecipeController và trả về recipes, trong vòng v-for sẽ render ra view và gán vào phần router trong app.js


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í