通过-Laravel-创建一个-Vue-单页面应用三

38次阅读

共计 8853 个字符,预计需要花费 23 分钟才能阅读完成。

文章转发自专业的 Laravel 开发者社区,原始链接:https://learnku.com/laravel/t…

我们将通过演示在 vue-router 进入一个路由 之前,如何异步加载数据来继续使用 Laravel 构建我们的 Vue SPA

之前在 通过 Laravel 创建一个 Vue 单页应用(二)中完成了 UsersIndex 组件异步地从 API 中加载用户。简化了从数据库构建一个真实的后端 API,选择通过 Laravelfactory() 方法在 API 返回中模拟假数据。

如果你还没有读过通过 Laravel 构建 Vue 单页应用的 第一部分 和 第二部分,我建议你先去看看,再回到这里。我会在这里等你。

这篇教程,我们将把模拟的 /users 返回替换为真正的由数据库支撑的。我习惯使用 MySQL,但是你可以使用任何你想用的数据库驱动!

UsersIndex.vue 路由组件在生命周期 created() 中通过 API 加载数据。下面是第二部分结尾的 fetchData() 方法示例:

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 输出一些真实数据。

创建一个真正的用户端点

我们将创建一个 UsersController 使用 Laravel 5.5 新的 API 资源 来返回 JSON 数据。

在创建控制器和 API 资源之前,让我们首先设置一个数据库并且进行数据填充,以便为我们的 SPA 提供一些测试数据。

用户数据填充

我们使用 make:seeder 命令来创建一个用户填充:

php artisan make:seeder UsersTableSeeder

UsersTableSeeder 非常简单。我们使用模型工厂创建 50 个用户:

<?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 部分 - 编辑现有用户

正文完
 0