内容简介:在上篇教程中,学院君已经完成了待办任务项目后端 API 接口的编写和功能测试,现在,我们开始编写 Vue 组件来实现前端的交互界面。首先在在这个 Vue 组件中,我们会通过父组件传入的
在上篇教程中,学院君已经完成了待办任务项目后端 API 接口的编写和功能测试,现在,我们开始编写 Vue 组件来实现前端的交互界面。
编写前端 Vue 组件
首先在 resources/js/components
目录新增一个 Vue 组件 TasksComponent.vue
,并编写模板代码和脚本代码如下:
<template> <div class="w-full sm:w-1/2 lg:w-1/3 rounded shadow"> <h2 class="bg-yellow-dark text-sm py-2 px-4 font-hairline font-mono text-yellow-darker">Tasks</h2> <ul class="list-reset px-4 py-4 font-serif bg-yellow-light h-48 overflow-y-scroll scrolling-touch"> <li v-for="(task, index) in tasks" class="flex"> <label class="flex w-5/6 flex-start py-1 block text-grey-darkest font-bold cursor-pointer"> <input class="mr-2 cursor-pointer" type="checkbox" :dusk="`check-task${task.id}`" :checked="checked(task)" @click="completeTask(task)" > <span :class="[{'line-through' : task.is_completed}, 'text-sm italic font-normal']"> {{ task.text }} </span> </label> <span class="flex-1 cursor-pointer text-center rounded-full px-3 text-yellow-light hover:text-yellow-darker text-xs py-1" @click="removeTask(index, task)" :dusk="`remove-task${task.id}`" >✖</span> </li> </ul> <form class="w-full text-sm" @submit.prevent="createTask"> <div class="flex items-center bg-yellow-lighter py-2"> <input class="appearance-none bg-transparent border-none w-3/4 text-yellow-darkest mr-3 py-1 px-2 font-serif italic" type="text" placeholder="New Task" aria-label="New Task" v-model="newTask" dusk="task-input" > <button class="flex-no-shrink bg-yellow hover:bg-yellow font-base font-normal text-yellow-darker py-2 px-4 rounded" type="button" dusk="task-submit" @click="createTask" > Add </button> </div> </form> </div> </template> <script> export default { props: ['initialTasks'], data() { return { newTask: '', tasks: this.initialTasks } }, methods: { createTask(event) { if (this.newTask.trim().length === 0) { return; } axios.post('/api/task', { text: this.newTask }).then((response) => { this.tasks.push(response.data); this.newTask = ''; }).catch((e) => console.error(e)); }, completeTask(task) { let status = ! task.is_completed; axios.put(`/api/task/${task.id}`, { is_completed: status }).then((response) => { task.is_completed = response.data.is_completed }).catch((e) => console.error(e)); }, checked(task) { return task.is_completed; }, removeTask(index, task) { axios.delete(`/api/task/${task.id}`) .then((response) => { this.tasks = [ this.tasks.slice(0, index), this.tasks.slice(index + 1) ]; }).catch((e) => console.error(e)); } } } </script>
在这个 Vue 组件中,我们会通过父组件传入的 initialTasks
属性来完成待办任务列表的渲染,然后我们还可以在组件中通过 Axios 库与后端 API 接口交互实现新增任务,移除任务,以及将任务标记为已完成。
接下来,我们需要将这个 Vue 组件注册到全局 Vue 实例,这个工作在 resources/js/app.js
中完成:
... Vue.component('tasks-component', require('./components/TasksComponent.vue').default); ...
编写前端视图模板
将 CSS 框架切换为 Tailwind CSS
到这里还没有结束,我们还要将上述 Vue 组件嵌入到视图模板中才能在前端显示出来。为此,我们还要编写相应的前端视图文件和布局文件,Laravel 默认的 CSS 框架是 Bootstrap,这里学院君想换个口味,使用 Tailwind CSS 来替代框架预设的 Bootstrap 样式( Tailwind CSS 对应的中文文档 在这里 ),这可以通过一个 Laravel 扩展包来快速切换,我们通过 Composer 来安装这个扩展包:
composer require laravel-frontend-presets/tailwindcss
然后运行如下 Artisan 命令执行切换:
php artisan preset tailwindcss
该命令会将 package.json
中 Bootstrap 相关扩展包替换成 Tailwind 的,并且删除 resources/sass
目录,将 Tailwind 资源文件发布到 resources/css
及 resources/js
目录,更新 resources/views/welcome.blade.php
视图文件和 webpack.mix.js
文件。
如果你需要更新框架自带的用户认证相关视图脚手架代码,还可以运行如下命令进行切换,建议执行这个命令,因为它会替我们生成后面要用到的认证路由、控制器和视图相关文件(运行这个命令就不必运行上一个 preset
命令了):
php artisan preset tailwindcss-auth
至此,从 Bootstrap 框架切换到 Tailwind CSS 框架的工作就完成了。
编写视图模板文件
我们将任务列表 Vue 组件的渲染放到 resources/views/home.blade.php
视图文件中,修改该视图模板代码如下:
@extends('layouts.app') @section('content') <div class="container px-4 sm:px-0 mx-auto py-8"> <tasks-component :initial-tasks="{{ $tasks }}"></tasks-component> </div> @endsection
该视图继承自 layouts.app
布局,我们在里面嵌入了前面注册的 tasks-component
组件,并且通过 initial-tasks
属性将控制器传过来的任务列表传入 Vue 组件(就是前面提到的 initialTasks
属性),由于我们把前端逻辑都封装到 Vue 组件中了,所以这个视图模板非常简洁。
编译前端资源
到这里,前端视图和 Vue 组件都编写好了,接下来我们需要编译前端资源,以便让前端视图可以正常渲染和使用,首先需要运行如下命令安装 package.json
中定义的前端资源依赖:
npm install
在编译前端资源前,需要对前端编译编排文件 webpack.mix.js
稍作修改:
const mix = require('laravel-mix'); /* |-------------------------------------------------------------------------- | Mix Asset Management |-------------------------------------------------------------------------- | | Mix provides a clean, fluent API for defining some Webpack build steps | for your Laravel application. By default, we are compiling the Sass | file for the application as well as bundling up all the JS files. | */ require('laravel-mix-purgecss') mix.js('resources/js/app.js', 'public/js') .postCss('resources/css/app.css', 'public/css') .options({ postCss: [ require('postcss-import')(), require('tailwindcss')(), require('postcss-cssnext')({ // Mix adds autoprefixer already, don't need to run it twice features: { autoprefixer: false } }), ] }) .purgeCss();
这里面有到了两个额外的依赖,需要安装后才能进行编译:
npm install postcss-import postcss-cssnext
做好了以上准备工作,接下来就可以运行如下命令来编译前端资源了:
npm run dev
编写后端代码
为了让前端视图可以正常渲染,页面交互功能可以正常使用,我们最后还要对后端代码做一些调整。
HomeController
我们希望用户登录之后才能访问待办任务列表,用户登录之后默认跳转的路由是 /home
,该路由对应的控制器方法是 HomeController@index
(在 routes/web.php
中可以看到),所以我们在 app/Http/Controllers/HomeController.php
中编写相应的业务逻辑代码如下:
public function index() { $tasks = auth()->user()->tasks->all(); return view('home', ['tasks' => json_encode($tasks)]); }
我们将认证用户名下关联的任务列表作为参数传递给 home
视图,为了让这段代码生效,还要在 User
模型类中新增一个 tasks
关联方法:
public function tasks() { return $this->hasMany(Task::class); }
单页面应用的认证实现
到目前为止,我们编写的这个待办任务项目算得上是个前后端分离的单页面应用,因为任务的增、删、改都是通过前端组件调用后端 API 接口异步实现的,后端 API 接口需要基于 API 进行认证,而我们之前介绍 API 认证时正好介绍过这种场景的认证实现: 用户认证与授权系列 —— 通过 Passport 实现 API 请求认证:单页面应用篇 ,这里我们同样借鉴这个思路来实现基于 Session 的登录认证与基于 Passport 实现的 API 认证的一体化。
首先打开 config/auth.php
,将 guards
配置项中的 api.driver
配置值修改为 passport
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', 'hash' => false, ], ],
然后在 app/Http/Kernel.php
中,添加 \Laravel\Passport\Http\Middleware\CreateFreshApiToken
中间件到 web
中间件组:
protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class ], ...
通过这个中间件可以实现用户通过表单登录后将访问令牌保存到 Cookie 中,以便在 API 认证时使用,这样就完成用户 Session 认证和 API 认证的一体化了。
至此,待办任务项目前后端的功能代码都已经编写好了,下面我们可以基于 Laravel Dusk 编写浏览器测试用例了。
基于 Dusk 实现浏览器测试
初始化 Dusk
使用 Dusk 之前,先通过 Composer 安装 Dusk 扩展包:
composer require --dev laravel/dusk
然后运行如下 Artisan 命令初始化 Dusk(在 tests
命令下创建 Browser
子目录及相关示例文件):
php artisan dusk:install
编写浏览器测试用例
通过如下命令创建一个新的浏览器测试用例:
php artisan dusk:make TasksTest
该命令会在 tests/Browser
目录下创建 TasksTest.php
文件,编写该测试用例文件代码如下:
<?php namespace Tests\Browser; use App\Task; use App\User; use Tests\DuskTestCase; use Laravel\Dusk\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations; class TasksTest extends DuskTestCase { use DatabaseMigrations; protected $user; /** * 通过模型工厂初始化测试用户 */ protected function setUp(): void { parent::setUp(); $this->user = factory(User::class)->create(); } /** * 测试创建任务 * @throws \Throwable */ public function testCreateTask() { $this->browse(function (Browser $browser) { // 以认证用户身份测试访问待办任务首页 $browser->loginAs($this->user) ->visit('/') ->assertSee('Tasks'); /** * 测试新增一个待办任务: * 输入「First Task」-> 点击提交「Add」-> 提交成功后断言列表里出现刚刚新增的任务 */ $browser ->waitForText('Tasks') ->type('@task-input', 'First Task') ->click('@task-submit') ->waitForText('First Task') ->assertSee('First Task'); /** * 测试新增第二个任务 */ $browser->type('@task-input', 'Second Task') ->press('@task-submit') ->waitForText('Second Task') ->assertSee('Second Task'); // 断言数据库是否包含刚刚新增的任务 $this->assertDatabaseHas('tasks', ['text' => 'First Task']); $this->assertDatabaseHas('tasks', ['text' => 'Second Task']); }); } /** * 测试移除任务 * @throws \Throwable */ public function testRemoveTask() { // 使用模型工厂创建一个待测试任务「Test Task」 $task = factory(Task::class)->create([ 'text' => 'Test Task', 'user_id' => $this->user->id ]); $this->browse(function (Browser $browser) { // 以认证用户身份访问首页 $browser ->loginAs($this->user) ->visit('/') ->waitForText('Tasks'); // 点击移除任务按钮,0.5秒后断言任务是否已删除(对应任务不存在) $browser->click("@remove-task1") ->pause(500) ->assertDontSee('Test Task'); }); // 断言数据库不包含对应任务确认后端删除成功 $this->assertDatabaseMissing('tasks', $task->only(['id', 'text'])); } /** * 测试完成任务(修改) * @throws \Throwable */ public function testCompleteTask() { // 还是使用模型工厂创建一个测试任务 $task = factory(Task::class)->create(['user_id' => $this->user->id]); $this->browse(function (Browser $browser) use ($task) { // 以认证用户身份访问首页并勾选任务已完成, // 如果 `line-through` 选择器出现则说明操作成功 $browser ->loginAs($this->user) ->visit('/') ->waitForText('Tasks') ->click("@check-task{$task->first()->id}") ->waitFor('.line-through'); }); // 断言数据库已完成任务不为空来确认后端数据库记录已更新 $this->assertNotEmpty($task->fresh()->is_completed); } }
在该浏览器测试用例中,我们仍然使用了 DatabaseMigrations
Trait 在测试用例运行前后重构和回滚所有数据库变更,以免产生脏数据,然后我们使用 setUp
方法在测试用例运行之前通过模型工厂创建一个初始测试用户,接下来编写了三个具体的测试用例,分别用于测试任务的创建、移除和修改,在这些测试用例中我们通过 $browser
实例模拟浏览器页面的访问、登录、表单输入、按钮点击等操作,从而完成相应的后端 API 调用,并且根据按钮、元素点击后页面的变化来断言相应的操作结果是否符合预期(更多断言方法与元素交互细节可以参考Dusk 文档),最后,还通过对数据库记录进行断言来确认前端操作是否生效(数据库断言及测试的更多细节请参考数据库测试文档)。
注:由于后端任务的创建、删除和修改 API 接口都需要认证后才能访问,所以我们通过浏览器实例的 loginAs
方法模拟用户 Web 登录,同时由于在 web
中间件组中应用了 CreateFreshApiToken
中间件,用户登录后将访问令牌保存到 Cookie 中,这样下次用户访问需要认证的 API 接口时就可以直接通过这个令牌判断用户已经登录了,从而实现了两种渠道认证的无缝对接。
运行浏览器测试用例
至此,浏览器测试用例编写完成,并且覆盖了所有 Vue 组件中涉及到的与后端 API 交互的方法,下面运行这个浏览器测试用例(运行之前先删除系统自带的 tests/Browser/ExampleTest.php
用例文件,因为我们已经调整过首页逻辑,所以该测试用例会运行失败),绿色代表测试通过:
这样一来,说明我们编写的前端视图和 Vue 组件功能无碍,可以进行后续其他功能的迭代了。
项目整体体验
前面所有功能的编写和测试都是通过代码完成的,到目前为止,我们还不知道项目的页面是什么样子,既然前面的测试表明项目的各项功能已经通过验收,下面不妨来看下庐山真面目。
由于我们在测试用例中都使用了 DatabaseMigrations
Trait,测试用例运行完成后,数据库的所有更改都回滚了,所以在体验之前,需要运行 php artisan migrate
创建所有数据表。
然后通过 http://todoapp.test
访问应用首页,经过 Tailwind CSS 渲染的首页长这样:
要访问待办任务页面,需要用户先登录,为此,我们来注册个新用户:
注册成功后,页面跳转到 /home
路由,此时待办任务列表为空:
由于我们的前端功能和后端功能都已经通过测试验收,所以大胆的对任务进行增删改查好了:
到这里,我们的测试驱动开发项目就告一段落,但是这里不是终点,后续介绍广播、缓存、队列、事件时还会基于此项目进行迭代。下一篇,我们将探索如何对 Laravel 项目进行持续集成。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- [ Laravel从入门到精通 ] 测试系列 —— 通过测试驱动开发构建待办任务项目(一):后端 API 接口篇
- 如何使用React Hooks建立一个待办事项列表
- 产品待办事项列表梳理 Product Backlog Grooming
- 禅道 9.8.stable 发布,增强待办功能和消息通知功能
- 喧喧 1.5.0 优化服务器性能,支持将消息创建为然之待办
- 微软待办事项To-Do Windows 10 UWP版更新:支持多账户切换
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
YES!产品经理(上、下册)
汤圆、老马 / 电子工业出版社 / 2011-9-1 / 128.00元
《YES!产品经理(套装上下册)》是一本融合了经管、工具和职场小说特点的图书,作者是国内产品经理咨询界最有实力的团队。 《YES!产品经理(套装上下册)》以职场小说的形式全面介绍产品管理、产品经理相关的知识,所有的问答均放置在设计好的101个情节中,同时每一个情节之间也都有相应的联系,读者能够从具体的情节走向中不但了解到产品管理的完整知识,而且能够深刻感受到一个产品经理的现实工作状态,从知识......一起来看看 《YES!产品经理(上、下册)》 这本书的介绍吧!
Base64 编码/解码
Base64 编码/解码
RGB CMYK 转换工具
RGB CMYK 互转工具