接續前一篇 Vue.js (11) - 在 Laravel 5.4 中使用 Vue 2.1,這一篇將要實戰如何寫出一個 CRUD 的應用,也就是對資料庫做建立、讀取、更新及刪除的動作。
我們將從無到有實際寫一個簡單的文章管理應用,整個打造的流程大致如下:
- 設定資料庫:為了示範方便,將使用 SQLite。
- 建立 API Routes:我們會透過 API 對資料庫做請求的動作。
- 建立 Post 頁面:在 Laravel 頁面中放入 Post.vue 元件。
- 建立 Post vue 元件:這裡是和使用者互動的部份,藉由前面建立的 API 來操作資料。
設定資料庫
你可以使用其他資料庫管理系統,例如 MySQL,這裡為了示範方便使用 SQLite。1. 建立資料庫檔案
在專案目錄的database
目錄下,執行:
touch sqlite.db
2. 修改 .env
將資料庫的設定改成以下內容:DB_CONNECTION=sqlite
DB_DATABASE=/絕對路徑/laravel-vue/database/sqlite.db
3. 產生 Post 模型及遷移檔
在專案根目錄下執行:php artisan make:model Post -m
使用 make:model 模型名稱
建立資料模型,-m
是同時建立資料庫遷移檔。這裡是為了簡化操作,你也可以分開建立。執行後會產生
app/Post.php
及 database/migrations/2017_06_06_073431_create_posts_table.php
兩個檔案。其中的 2017_06_06_073431_
會因建立的時間而不同。
4. 編輯 2017_06_06_073431_create_posts_table.php:
PHPpublic function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title'); // 文章的標題
$table->string('body'); // 文章的內容
$table->timestamps();
});
}
存檔,然後執行:
php artisan migrate
這樣資料庫的表格及欄位就建好了,你可以用指令或 SQLPro for SQLite 軟體來查看資料庫的內容。
建立 API Routes
註:請先啟動內建伺服器php artisan serve
,確定網站是可以正常運作的。我們會建立 5 個 API :
GET http://127.0.0.1:8000/api/posts
:取得全部文章GET http://127.0.0.1:8000/api/posts/{id}
:取得單一文章POST http://127.0.0.1:8000/api/posts
:建立一篇文章PUT http://127.0.0.1:8000/api/posts/{id}
:更新一篇文章DELETE http://127.0.0.1:8000/api/posts/{id}
:刪除一篇文章
routes/api.php
Laravel 5.4 已經內建 API 機制,所有在api.php
建立的 routes,網址都會以 api
開頭,像這樣 http://127.0.0.1:8000/api
。這裡有一點要說明,為了示範單純化,我們這裡建立的 API 全都是未受限制的,也就是任何知道網址的人就能存取,這樣其實很危險。在正規的作法下,必須只接受登入後的存取,可以使用中介軟體來達成:
Route::middleware('auth:api')->get(...略);
這樣中介軟體就會過濾掉未登入的操作,記得在正式的產品中,務必加入驗證機制。
取得全部文章
目前資料庫中是空的,我們先用 Tinker 來加入一筆資料:php artisan tinker
這樣會進入 Tinker 的互動模式。Tinker 可以和你的應用程式做互動,其中包括 Eloquent。之前用指令產生的
Post.php
被放在 app
目錄下,因此完整名稱為 App\Post
,我們用它來列出所有文章:
App\Post::all();
應該會是空陣列,因為我們還沒新增資料。動手來新增一筆吧:
// 以下輸入一行可以按 Enter 執行
$post = new App\Post;
$post->title = "這是文章標題";
$post->body = "這是文章內容";
$post->save();
// 到這裡如果回傳 true 表示資料新增成功了
// 再來看一次結果
App\Post::all();
應該會看到我們新增的文章。現在開啟 api.php
加入我們的第一個 API Route:
PHPRoute::get('/posts', function() {
return response()->json(App\Post::all(), 200);
});
打開瀏覽器進入 http://127.0.0.1:8000/api/posts
看看是否有收到 JSON 格式的資料。
取得單一文章
接著加入取得單一文章的 API Route:PHPRoute::get('/posts/{id}', function($id) {
return response()->json(App\Post::find($id), 200);
});
看看 http://127.0.0.1:8000/api/posts/1
應該有資料。這個 Route 之後不會用到,單純示範。
建立一篇文章
註:為了示範單純,這裡都不做任何輸入資料的驗證。加入一個建立文章的 API Route:
PHPRoute::post('/posts/', function(Request $request) {
$post = new Post;
$post->title = $request->input('title', '沒有標題');
$post->body = $request->input('body', '沒有內文。');
$ok = $post->save();
return response()->json(['ok' => $ok], 200);
});
接收 POST (這裡是指表單傳送的方法) 來新增資料,新增後回傳 ok
的值告知是否成功。接著你可以用 Postman 這個工具來測試,或是用 curl 命令列工具,執行:
curl -d "title=第2篇文章&body=第2篇文章的內容" http://127.0.0.1:8000/api/posts
得到回傳結果 {"ok":true}
表示成功了。回到 http://127.0.0.1:8000/api/posts
看看,應該會多出一筆資料。-d
選項是指將它之後的字串內容以 POST 方法傳送到指定的網址。前面取得文章的動作也可以用 curl 來測試:
curl http://127.0.0.1:8000/api/posts
curl http://127.0.0.1:8000/api/posts/1
更新一篇文章
加入一個更新文章的 API Route:PHPRoute::put('/posts/{id}', function(Request $request, $id) {
$ok = false;
$msg = '';
//
$post = Post::find($id);
if ($post) {
$post->title = $request->input('title', '沒有標題');
$post->body = $request->input('body', '沒有內文。');
$ok = $post->save();
if (!$ok) $msg = '更新失敗!';
} else {
$msg = '找不到文章';
}
return response()->json(['ok' => $ok, 'msg' => $msg], 200);
});
測試更新文章:
curl -X PUT -d "title=第2篇文章修改&body=第2篇文章的內容修改" http://127.0.0.1:8000/api/posts/2
回傳 {"ok":true,"msg":""}
表示成功。你可以試試看更新找不到的 id 是否會回傳錯誤訊息。回到 http://127.0.0.1:8000/api/posts
看看結果。-X
選項可以讓你自定傳送的方法名稱,其他選項和 POST 相同,但是注意網址要附上文章 id。
刪除一篇文章
PHPRoute::delete('/posts/{id}', function($id){
$rows = Post::destroy($id);
$ok = ($rows > 0);
return response()->json(['ok' => $ok], 200);
});
測試:
curl -X DELETE http://127.0.0.1:8000/api/posts/2
回傳結果 {"ok":true}
表示成功了。回到 http://127.0.0.1:8000/api/posts
看看結果。
PostController.php
前面我們將 API 的執行工作全都寫在api.php
中,當功能變多時,夾雜大量的處理邏輯將會變得很混亂,以下將重構處理邏輯,將它們移到相對應的 Controller 中。執行以下指令:
php artisan make:controller PostController
會建立 app/Http/Controllers/PostController.php
,然後將我們在 api.php
中建立的處理邏輯移過來:
PHP<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Post;
class PostController extends Controller
{
// API
/**
* 取得全部文章
*/
function apiAll() {
return response()->json(Post::all(), 200);
}
/**
* 取得單一文章
*/
function apiFindPostById($id) {
return response()->json(Post::find($id), 200);
}
/**
* 建立一篇文章
*/
function apiCreatePost(Request $request) {
$post = new Post;
$post->title = $request->input('title', '沒有標題');
$post->body = $request->input('body', '沒有內文。');
$ok = $post->save();
return response()->json(['ok' => $ok], 200);
}
/**
* 更新一篇文章
*/
function apiUpdatePostById(Request $request, $id) {
$ok = false;
$msg = '';
//
$post = Post::find($id);
if ($post) {
$post->title = $request->input('title', '沒有標題');
$post->body = $request->input('body', '沒有內文。');
$ok = $post->save();
if (!$ok) $msg = '更新失敗!';
} else {
$msg = '找不到文章';
}
return response()->json(['ok' => $ok, 'msg' => $msg], 200);
}
/**
* 刪除一篇文章
*/
function apiDeletePostById($id) {
$rows = Post::destroy($id);
$ok = ($rows > 0);
return response()->json(['ok' => $ok], 200);
}
}
接著修改 api.php
,改為使用 PostController:
PHPRoute::get('/posts', 'PostController@apiAll');
Route::get('/posts/{id}', 'PostController@apiFindPostById');
Route::post('/posts', 'PostController@apiCreatePost');
Route::put('/posts/{id}', 'PostController@apiUpdatePostById');
Route::delete('/posts/{id}', 'PostController@apiDeletePostById');
這樣是不是清爽多了~
建立 Post 頁面
API 都打造完成之後,接下來就要讓使用者可以使用它們來操作內容。routes/web.php
在這裡建立的 Route 是可以讓使用者連入的網址,我們一樣將處理邏輯分開:PHPRoute::get('/posts', 'PostController@index');
然後在 PostController.php
加上一個新方法:PHP/**
* 文章首頁
*/
function index()
{
return view('post');
}
// API
...略
非常簡單的只回傳一個 view。
新增 resources/views/post.blade.php
我們會用到前一篇建立的 Layout ,所以內容如下:@extends('layouts.default')
@section('title', 'Make CRUD App By Laravel with Vue')
@section('content')
<Post></Post>
@endsection
@section('script')
<script src="/js/post.js"></script>
@endsection
關於 CSS
之前的 Layout 檔沒有用到 CSS,現在來補加,首先編輯webpack.mix.js
:
JavaScriptmix.js('resources/assets/js/hello.js', 'public/js')
.extract(['lodash','jquery','axios','vue'])
.sass('resources/assets/sass/app.scss', 'public/css');
加上 sass
的部分,這是 Laravel 內建的,可以調整成自己想要的。再來是把打包後的 app.css
加入 Layout 中。編輯
resources/views/layouts/default.blade.php
:
HTML...略
<title>@yield('title')</title>
<link rel="stylesheet" href="/css/app.css">
</head>
...略
以上在 Laravel 中關於頁面的部份就完成,接下來終於進入 Vue 的部分了。
建立 Post vue 元件
新增 resources/assets/js/components/Post.vue
HTML<template>
<div class="content">
<div v-for="post in posts">
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
<button class="btn btn-xs btn-primary" @click="modify(post)">修改</button>
<button class="btn btn-xs btn-danger" @click="remove(post.id)">刪除</button>
<hr>
</div>
<form id="form">
<div class="form-group" :class="{ 'has-warning': titleWarning }">
<label class="control-label">標題
<span v-if="titleWarning">不能空白</span>
</label>
<input class="form-control" v-model="post.title">
</div>
<div class="form-group" :class="{ 'has-warning': bodyWarning }">
<label class="control-label">內容
<span v-if="bodyWarning">不能空白</span>
</label>
<textarea class="form-control" v-model="post.body"></textarea>
</div>
<div class="form-group">
<div v-if="isSave">
<button @click.prevent="save">儲存</button>
<button @click.prevent="cancel">取消</button>
</div>
<button v-else @click.prevent="publish">發佈</button>
</div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
posts: [],
post: {
id: null,
title: '',
body: ''
},
titleWarning: false,
bodyWarning: false,
isSave: false
}
},
methods: {
init: function () {
let self = this;
axios.get('/api/posts')
.then(function (response) {
self.posts = response.data;
})
.catch(function (response) {
console.log(response);
});
},
publish: function () {
this.titleWarning = (this.post.title.trim().length == 0);
this.bodyWarning = (this.post.body.trim().length == 0);
if (this.titleWarning || this.bodyWarning) return;
//
let self = this;
axios.post('/api/posts', this.post)
.then(function (response) {
if (response.data['ok']) {
self.init();
self.titleWarning = false;
self.bodyWarning = false;
self.post = {id:null, title: '', body:''};
}
})
.catch(function (response) {
console.log(response)
});
},
modify: function (post) {
location.href = "#form";
this.post.id = post.id;
this.post.title = post.title;
this.post.body = post.body;
this.isSave = true;
console.log(this.post);
},
save: function () {
let self = this;
axios.put('/api/posts/' + this.post.id, this.post)
.then(function (response) {
if (response.data['ok']) {
self.init();
self.isSave = false;
self.post = {id:null, title: '', body:''};
}
})
.catch(function (response) {
console.log(response);
});
},
cancel: function () {
this.post = {id: null, title: '', body: ''};
this.isSave = false;
},
remove: function (id) {
let self = this;
axios.delete('/api/posts/' + id)
.then(function (response) {
if (response.data['ok']) {
self.init();
}
})
.catch(function (response) {
console.log(response);
});
}
},
mounted: function () {
this.init();
}
}
</script>
<style scoped>
.content {
padding: 20px;
}
</style>
說明:
- 頁面的排版是上方顯示文章清單,同時附上「修改」與「刪除」的按鈕。
- 接著是一個表單,可同時做為「發佈」新文章及「儲存」修改的內容。
- 在 data() 中,
posts
會儲存全部的文章,post
會和表單的欄位綁定,可做為發佈及修改時的資料。其他 3 個是用來得知狀態改變時用的。 - methods 中,請求都是透過我們建立的 API
- init 用來初始化資料,所以它會去取得全部的文章。
- axios 是一個基於 Promise 的 HTTP 請求套件,由於在其內部中無法使用
this
來存取外部的資料,所以透過let self = this;
轉傳。它是以get().then().catch()
的方式來請求資料,成功時呼叫then()
失敗時呼叫catch()
- publish 會送出新增文章的請求,成功的話執行 init 來取得全部文章。
- modify 會將資料送給
post
,由於它和表單綁定,所以使用者就可以直接在表單上看到內容。 - save 會送出更新文章的請求,成功的話執行 init 來取得全部文章。
- cancel 會取消表單中的內容,並且隱藏「儲存」及「取消」按鈕。
- remove 會送出刪除文章的請求,成功的話執行 init 來取得全部文章。
- mounted 會在網頁載入完成時執行,這裡我們執行 init 方法。
新增 resources/assets/js/post.js
JavaScriptrequire('./bootstrap.js');
import Post from './components/Post.vue';
new Vue({
el: '#app',
components: { Post }
})
webpack.mix.js
JavaScriptmix.js('resources/assets/js/hello.js', 'public/js')
.js('resources/assets/js/post.js', 'public/js')
.extract(['lodash','jquery','axios','vue'])
.sass('resources/assets/sass/app.scss', 'public/css');
把我們新建立的 post.js
加入打包的動作中。
關於 CSRF
Laravel 已經內建 CSRF 保護機制,所以我們必須修改default.blade.php
:
HTML...略
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title')</title>
...略
在 <head>
中加上相關的 meta 值。它被用於 resources/assets/js/bootstrap.js
中,Laravel 已經幫我們指定給 axios 了。如果你少了這個動作,所有表單的送出請求都會被禁止。好了,全部完成,執行自動打包來看看結果
npm run watch
或
yarn run watch
在瀏覽器中開啟 http://127.0.0.1:8000/posts
試用看看自己打造的應用程式吧~以下是執行結果:
本文網址:http://blog.tonycube.com/2017/06/vuejs-12-crud-laravel-vue.html
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
我要留言
留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。