經過 Laravel 建立一個 Vue 單頁面應用(三)

文章轉發自專業的Laravel開發者社區,原始連接: https://learnku.com/laravel/t...

咱們將經過演示在 vue-router 進入一個路由以前,如何異步加載數據來繼續使用 Laravel 構建咱們的 Vue SPAphp

以前在 經過 Laravel 建立一個 Vue 單頁應用(二) 中完成了 UsersIndex 組件異步地從 API 中加載用戶。 簡化了從數據庫構建一個真實的後端 API,選擇經過 Laravelfactory() 方法在 API 返回中模擬假數據。html

若是你尚未讀過經過 Laravel 構建 Vue 單頁應用的 第一部分 和 第二部分,我建議你先去看看,再回到這裏。我會在這裏等你。vue

這篇教程,咱們將把模擬的 /users 返回替換爲真正的由數據庫支撐的。我習慣使用 MySQL,可是你可使用任何你想用的數據庫驅動!mysql

UsersIndex.vue 路由組件在生命週期 created() 中經過 API 加載數據。下面是第二部分結尾的 fetchData() 方法示例:ios

created() {
    this.fetchData();
},
methods: {
    fetchData() {
        this.error = this.users = null;
        this.loading = true;
        axios
            .get('/api/users')
            .then(response => {
                this.loading = false;
                this.users = response.data;
            }).catch(error => {
                this.loading = false;
                this.error = error.response.data.message || error.message;
            });
    }
}

我將演示若是經過組件的前置導航從 API 中提取數據,可是這以前咱們須要讓 API 輸出一些真實數據。laravel

建立一個真正的用戶端點

咱們將建立一個 UsersController 使用 Laravel 5.5 新的 API 資源 來返回 JSON 數據。git

在建立控制器和 API 資源以前, 讓咱們首先設置一個數據庫而且進行數據填充,以便爲咱們的 SPA 提供一些測試數據。github

用戶數據填充

咱們使用 make:seeder 命令來建立一個用戶填充:vue-router

php artisan make:seeder UsersTableSeeder

UsersTableSeeder 很是簡單。咱們使用模型工廠建立 50 個用戶:sql

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        factory(App\User::class, 50)->create();
    }
}

接下來,咱們將 UsersTableSeeder 添加到 database/seeds/DatabaseSeeder.php 文件中:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $this->call([
            UsersTableSeeder::class,
        ]);
    }
}

若是不先建立和配置數據庫,咱們將不能使用數據填充。

配置數據庫

是時候給咱們的Vue SPA Laravel應用鏈接一個真實的數據庫了。你能夠經過使用相似TablePlus 的GUI工具來使用SQLite或者MySQL。若是你是Laravel的新手,你能夠查閱在數據庫入門上的大量文檔。

若是你有一個運行在你設備上的MySQL實例,你可使用如下命令行至關快速建立一個新數據庫(假設你本地環境沒有設置密碼):

mysql -u root -e"create database vue_spa;"

# 或者經過-p參數來輸入密碼
mysql -u root -e"create database vue_spa;" -p

當你有了數據庫,在 .env文件添加配置DB_DATABASE=vue_spa。若是你遇到了問題,請遵循文檔,這樣可使您的數據庫更容易地工做。

一旦你配置好了數據庫鏈接,你能夠遷移你的數據表和添加填充數據。Laravel附帶了一個Users表的遷移,咱們使用它來填充數據:

# 確保數據庫seeders自動加載
composer dump-autoload
php artisan migrate:fresh --seed

若是你願意,你也可使用單獨的artisan db:seed命令!就像這樣;你應該有一個包含50個用戶的數據庫,咱們能夠經過api查詢和返回。

Users 控制器

第二章, 模擬的 /users 在  routes/api.php 中長下面這樣:

Route::get('/users', function () {
    return factory('App\User', 10)->make();
});

咱們來新建一個控制器類,這樣能夠在生產環境使用 php artisan route:cache 來得到必定的益處,這種方式不支持閉包。咱們在命令行中同時建立控制器和 User API 資源類:

php artisan make:controller Api/UsersController
php artisan make:resource UserResource

第一命令是在  app/Http/Controllers/Api 目錄中建立一個 User 控制器,第二個命令在 app/Http/Resources 目錄中建立 UserResource

下面控制器和 Api 命名空間對應的的新 routes/api.php 代碼:

Route::namespace('Api')->group(function () {
    Route::get('/users', 'UsersController@index');
});

控制很直接;返回一個帶分頁的Eloquent API

<?php

namespace App\Http\Controllers\Api;

use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;

class UsersController extends Controller
{
    public function index()
    {
        return UserResource::collection(User::paginate(10));
    }
}

下面是一個 JSON 響應的例子,和以前  UserResource 的 API 格式相似:

{
   "data":[
      {
         "name":"Francis Marquardt",
         "email":"schamberger.adrian@example.net"
      },
      {
         "name":"Dr. Florine Beatty",
         "email":"fcummerata@example.org"
      },
      ...
   ],
   "links":{
      "first":"http:\/\/vue-router.test\/api\/users?page=1",
      "last":"http:\/\/vue-router.test\/api\/users?page=5",
      "prev":null,
      "next":"http:\/\/vue-router.test\/api\/users?page=2"
   },
   "meta":{
      "current_page":1,
      "from":1,
      "last_page":5,
      "path":"http:\/\/vue-router.test\/api\/users",
      "per_page":10,
      "to":10,
      "total":50
   }
}

很奇妙,Laravel 自動加上了分頁數據,而且將用戶信息分配到 data 屬性!

下面是 UserResource 類:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class UserResource extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }
}

UserResource 將集合中的每一個 User 模型轉換爲數組,提供 UserResource::collection() 方法將用戶的集合轉換爲 JSON 格式。

到如今,你應該有一個 /api/users 接口能夠用在單頁應用中,若是你繼續學看下去,你會注意到新的返回已經不知足當前的組件。

修改 UsersIndex 組件

咱們能夠經過調整 then() 來調用用戶數據所在的 data 鍵,來很快的讓 UsersIndex.vue 組件從新工做。剛開始的時候它看起來有點新穎,可是 response.data 是一個響應對象,所以咱們能夠這樣設置用戶數據:

this.users = response.data.data;

 fetchData() 和新 API 配合調整後的方法以下:

fetchData() {
    this.error = this.users = null;
    this.loading = true;
    axios
        .get('/api/users')
        .then(response => {
            this.loading = false;
            this.users = response.data.data;
        }).catch(error => {
            this.loading = false;
            this.error = error.response.data.message || error.message;
        });
}

導航前讀取數據

咱們的組件經過咱們新的API來運做,如今是演示如何在導航到組件以前獲取用戶信息的絕佳時機。
經過使用這種方法,咱們能夠在獲取數據以後導航到新路線。咱們能夠經過使用beforeRouteEnter 守衛在進入組件以前實現。例子Vue路由文檔以下:

beforeRouteEnter (to, from, next) {
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },

查閱文檔有完整的示例,但只需說咱們將異步獲取用戶數據,而且只有在完成以後咱們纔會觸發 next()和在組件裏設置數據(變量 vm )

檢查文檔以得到完整的示例,但只需說咱們將異步獲取用戶數據,一旦完成,而且只有在完成以後,咱們纔會觸發next(,並在組件上設置數據(變量vm)。

如下是getUsers函數可能看起來像是異步從API獲取用戶,而後觸發對組件的回調:

const getUsers = (page, callback) => {
    const params = { page };

    axios
        .get('/api/users', { params })
        .then(response => {
            callback(null, response.data);
        }).catch(error => {
            callback(error, error.response.data);
        });
};

注意,該方法不返回Promise,而是在完成或失敗時觸發回調。回調傳遞兩個參數:一個錯誤和來自API調用的響應。

咱們的getUsers()方法接受一個 page 變量,該變量最終做爲查詢字符串參數出如今請求中。若是爲空(路由中沒有傳遞頁碼),則API將默認設爲page=1

最後我要指出的是const params值。它其實是這樣的:

{
    params: {
        page: 1
    }
}

下面是咱們的beforeRouteEnter守衛如何使用getUsers 函數獲取異步數據,而後在組件上調用next()設置它:

beforeRouteEnter (to, from, next) {
    const params = {
        page: to.query.page
    };

    getUsers(to.query.page, (err, data) => {
        next(vm => vm.setData(err, data));
    });
},

這是從API返回數據後的 getUsers()調用中的callback參數:

(err, data) => {
    next(vm => vm.setData(err, data));
}

而後在API成功響應時,在getUsers()中這樣調用:

callback(null, response.data);

beforeRouteUpdate

當組件已經處於渲染狀態,而且路由更改時,將調用beforeRouteUpdate,而且Vue會在新路由中複用組件。例如,當咱們的用戶從/users?page=2跳轉到 /users?page=3

beforeRouteUpdate調用相似於beforeRouteEnter。可是,前者能夠在組件中使用 this,所以在樣式上會略有不一樣:

// 當路由更改而且組件已經渲染時,
// 邏輯會略有不一樣。
beforeRouteUpdate (to, from, next) {
    this.users = this.links = this.meta = null
    getUsers(to.query.page, (err, data) => {
        this.setData(err, data);
        next();
    });
},

因爲組件處於渲染狀態,咱們須要在從API獲取下一組用戶以前重置一些數據屬性。咱們能夠訪問組件。所以,咱們能夠先調用this.setData()(我尚未向您展現),而後不須要回調就調用next()

最後,這是在UsersIndex組件中的setData方法:

setData(err, { data: users, links, meta }) {
    if (err) {
        this.error = err.toString();
    } else {
        this.users = users;
        this.links = links;
        this.meta = meta;
    }
},

 setData()方法經過使用對象析構來獲取。
datalinks和 meta鍵來自於API的響應。咱們清晰地使用data: users將 data賦值給新變量users

一塊兒捆綁UsersIndex

我已經向您展現了該UsersIndex組件的各個部分,咱們已經準備好將全部組件捆綁在一塊兒,並進行一些很是基本的分頁。本教程未向您展現如何構建分頁,所以您能夠本身找到(或建立)奇特的分頁!

分頁是一種很好的方式,向您展現如何以vue-router編程方式瀏覽SPA 。

這是帶有咱們新的掛鉤和方法的完整組件,這些新掛鉤和方法可以使用路由器掛鉤獲取異步數據:

<template>
    <div class="users">
        <div v-if="error" class="error">
            <p>{{ error }}</p>
        </div>

        <ul v-if="users">
            <li v-for="{ id, name, email } in users">
                <strong>Name:</strong> {{ name }},
                <strong>Email:</strong> {{ email }}
            </li>
        </ul>

        <div class="pagination">
            <button :disabled="! prevPage" @click.prevent="goToPrev">Previous</button>
            {{ paginatonCount }}
            <button :disabled="! nextPage" @click.prevent="goToNext">Next</button>
        </div>
    </div>
</template>
<script>
import axios from 'axios';

const getUsers = (page, callback) => {
    const params = { page };

    axios
        .get('/api/users', { params })
        .then(response => {
            callback(null, response.data);
        }).catch(error => {
            callback(error, error.response.data);
        });
};

export default {
    data() {
        return {
            users: null,
            meta: null,
            links: {
                first: null,
                last: null,
                next: null,
                prev: null,
            },
            error: null,
        };
    },
    computed: {
        nextPage() {
            if (! this.meta || this.meta.current_page === this.meta.last_page) {
                return;
            }

            return this.meta.current_page + 1;
        },
        prevPage() {
            if (! this.meta || this.meta.current_page === 1) {
                return;
            }

            return this.meta.current_page - 1;
        },
        paginatonCount() {
            if (! this.meta) {
                return;
            }

            const { current_page, last_page } = this.meta;

            return `${current_page} of ${last_page}`;
        },
    },
    beforeRouteEnter (to, from, next) {
        getUsers(to.query.page, (err, data) => {
            next(vm => vm.setData(err, data));
        });
    },
    // when route changes and this component is already rendered,
    // the logic will be slightly different.
    beforeRouteUpdate (to, from, next) {
        this.users = this.links = this.meta = null
        getUsers(to.query.page, (err, data) => {
            this.setData(err, data);
            next();
        });
    },
    methods: {
        goToNext() {
            this.$router.push({
                query: {
                    page: this.nextPage,
                },
            });
        },
        goToPrev() {
            this.$router.push({
                name: 'users.index',
                query: {
                    page: this.prevPage,
                }
            });
        },
        setData(err, { data: users, links, meta }) {
            if (err) {
                this.error = err.toString();
            } else {
                this.users = users;
                this.links = links;
                this.meta = meta;
            }
        },
    }
}
</script>

若是更容易理解,這裏是做爲GitHub Gist的UsersIndex.vue

這裏有不少新事物,所以我將指出一些更重要的觀點。該goToNext()goToPrev()方法演示瞭如何使用導航vue-router使用this.$router.push

this.$router.push({
    query: {
        page: `${this.nextPage}`,
    },
});

咱們正在將新頁面推送到觸發的查詢字符串beforeRouteUpdate。我還要指出的是,我向您展現<button>了上一個和下一個動做的元素,主要是爲了演示經過編程方式進行導航的過程vue-router,您極可能會使用它<router-link />來自動在分頁路線之間導航。

我引入了三個計算屬性(nextPageprevPagepaginatonCount)來肯定下一頁和上一頁的頁碼,並paginatonCount顯示了當前頁碼的可視計數和總頁數。

下一個和上一個按鈕使用計算出的屬性來肯定是否應禁用它們,而goTo方法使用這些計算出的屬性將page查詢字符串參數推入下一頁或上一頁。當下一頁或上一頁在第一頁和最後一頁的邊界處爲空時,將禁用這些按鈕。

代碼中可能有一些冗餘,可是此組件說明vue-router了在進入路由以前用於獲取數據的方法!

不要忘記確保經過運行Laravel Mix構建最新版本的JavaScript:

# NPM
npm run dev

# Watch to update automatically while developing
npm run watch

# Yarn
yarn dev

# Watch to update automatically while developing
yarn watch

最後,這是咱們更新完整的UsersIndex.vue組件後顯示出的SPA結果:

下一步是什麼

咱們如今有一個有效的API,能夠從數據庫中獲取真實數據,還有一個簡單的分頁組件,該組件在後端使用Laravel的API模型資源進行簡單的分頁連接並將數據包裝在數據鍵中。

接下來,咱們將致力於建立,編輯和刪除用戶。 一個/ users資源將被鎖定在一個實際的應用程序中,可是目前,咱們只是在構建CRUD功能來學習如何與vue-router一塊兒使用來異步導航和提取數據。

咱們還能夠將axios客戶端代碼從組件中抽象出來,可是如今,這很簡單,所以咱們將其保留在組件中,直到第4部分。一旦添加了其餘API功能,咱們將想要建立專用的 HTTP客戶端的模塊。

您能夠繼續進行第4部分-編輯現有用戶

相關文章
相關標籤/搜索