[入门] Laravel 5.3 与 Vue 组件如何协作,轻松的完成前端的工作

Travel Image
2016.11.06,中国海南•海口


见过很多同学对写 JavaScript 代码都很头疼,因为后台代码的编写一般由框架规范了很多东西,大多数人其实都能快高效率的进入开发,而前端项目的代码就会粘贴复制居多,而且可复用性也很差。

Laravel 本身对前端开发就提供了很多支持,5.3 更是直接引入了 Vue 这个目前最流行的轻量级前端框架,这篇文章使用一个完整实际案例来讲讲如何在项目中使用 Vue 的 Reactivity 特性和组件化系统,让前端开发和后端无缝的结合在一起,让开发过程更愉悦,而且代码的可复用性也更强。

准备工作

实现「发送短信验证码」这个业务是个很不错的拿来演示的实际案例,在大多数项目中也用得到,前端代码不复杂,但也不简单,后端部分也需要接触到如何集成第三方服务、请求验证、缓存、队列、日志等 Laravel 框架基础的一些功能,不过先不用想那么复杂,我们来一步一步来搭建就可以。

创建全新的 Laravel 5.3 应用
composer create-project --prefer-dist laravel/laravel demo

为了方便演示,使用 make:auth 快速创建一些基本的页面:

php artisan make:auth
开发需求

Good job!现在看到登录界面之后,你接收到的任务(需求)就是,在用户忘记密码的时候进行发送重置链接之前,需要进行 手机号码的验证 :

Login Screen

完成后的效果如下,是不是很简单,不过我们在后面还是有很多工作需要处理的:

Reset Screen

去浏览一下文档中的 前端开发入手 是个不错的起点,不过在这里我们使用 yarn 来安装 npm 依赖包:

# 切换到项目根目录
yarn 
# 在开发的过程中运行
gulp watch
需要完成的任务列表:
  1. 添加 phone 字段到用户表;
  2. 添加 POST /api/phone/code 的接口来处理验证码的发送;
  3. 编写 Vue 前端组件;
  4. 将组件整合到页面当中;
  5. Done!
1. 添加 phone 字段到用户表;

运行下面的命令之前需要配置好数据库连接:

php artisan make:migration add_phone_to_users_table --table=users

# 迁移文件 `xxxx_add_phone_to_users_table.php` 内容:
public function up()
{
    Schema::table('users', function (Blueprint $table) {
        // Only consider China
        $table->string('phone', 11)->unique()->nullable();
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('phone');
    });
}

php artisan migrate
2. 添加 POST /api/phone/code 的接口来处理验证码的发送;

routes\api.php 文件中添加路由:

Route::post('phone/code', 'ApiController@sendVerifyCode');

创建 API 响应控制器,发送验证码的 Job;

php artisan make:controller ApiController

php artisan make:job SendVerifyCode

下面是代码示例:

文件 app\Controllers\ApiController.php 内容:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Jobs\SendVerifyCode;

class ApiController extends Controller
{
        public function sendVerifyCode(Request $request)
    {
                $this->validate($request, ['phone' => 'required|size:11|exists:users']);

        dispatch(new SendVerifyCode($request->phone));

        return ['success' => true];
    }
}

文件 app\Jobs\SendVrifyCode.php 内容:

<?php

namespace App\Jobs;

use PhpSms;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendVerifyCode implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    protected $phone;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($phone)
    {
        $this->phone = $phone;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        // Generate 4 digit random code
        $code = str_random(4);

        // Push it to cache storage, expired time: 10 mins
        \Cache::put($this->phone, $code, 10);

        $content = "【XXXX】您的验证码是{$code}";

        $templates = [
            'YunPian' => env('YUNPIAN_TEMPLATE_VERIFYCODE_ID', 'your_temp_id'),
        ];

        $templateData = [
            'code' => $code
        ];

        PhpSms::make()->to($this->phone)
            ->template($templates)
            ->data($templateData)
            ->content($content)
            ->send();
    }
}

使用了 PhpSms 这个扩展包和云片来完成短信发送的工作;

3. 编写 Vue 前端组件;

resources/assets/js/ 目录下, Laravel 已经帮你添加了一些代码,引入了 BootrapVue 这两个库,所以你需要处理的工作是在该目录下的 components 使用 Vue 的 单文件组件系统 组织代码,然后在 app.js 文件引入该组件就可以:

Vue.component('example', require('./components/Example.vue'));

让我们开始进入这篇文章的正题,在 resources/assets/js/components 文件下面新建文件 SendCodeField.vue 组件:

<template>
    <div class="form-group">
        <div class="form-group" :class="{'has-error': errorPhone}">
            <label for="phone" class="col-md-4 control-label">手机号码</label>

            <div class="col-md-4">
                <input type="text" class="form-control" name="phone" v-model="phone" required>

                <span class="help-block" v-show="errorPhone">
                    <strong>{{errorPhone}}</strong>
                </span>
            </div>
        </div>

        <label for="code" class="col-md-4 control-label">验证码</label>

        <div class="col-md-2">
            <input type="text" class="form-control" name="code" v-model="code" required>
            <span class="help-block" v-show="errorCode">
                <strong>{{errorCode}}</strong>
            </span>
        </div>
        <div class="col-md-2">
            <button type="button" 
                class="btn btn-default" 
                :class="{disabled: counter !== 0}"
                @click="sendCode">
                发送验证码 
                <span v-show="counter > 0">({{counter}})</span>
            </button>
        </div>
    </div>
</template>
<script>
    export default{
        name: 'sendCodeField',
        props: [],
        data () {
            return {
                phone: null,
                code: null,
                counter: 0
            }
        },
        computed: {
            errorPhone () {
                if (this.phone == null || this.checkPhone(this.phone)) {
                    return false
                }

                return '手机号码不正确!';
            },
            errorCode () {
                if (this.code == null || this.code.length == 4) {
                    return false
                }

                return '4 位验证码'
            }
        },
        methods: {
            startCount (value = 60) {
                this.counter = value

                var self = this
                var clock = setInterval(function () {
                    if (self.counter === 0) {
                        clearInterval(clock)
                        return
                    }
                    self.counter -= 1
                }, 1000)
            },
            sendCode () {
                if (this.phone == null || this.counter > 0 || !this.checkPhone(this.phone)) {
                    return
                }

                var self = this
                this.$http.post('/api/phone/code', {phone: this.phone})
                    .then(function (response) {
                        self.startCount()
                    })
                    .then(function (error) {
                        console.log(error)
                    })
            },
            checkPhone: function (phone) {
                return !!phone.match(/^(0|86|17951)?(13[0-9]|15[012356789]|17[0678]|18[0-9]|14[57])[0-9]{8}$/)
            }
        }
    }
</script>

然后不要忘记在 app.js 中引入该组件:

Vue.component('send-code-field', require('./components/SendCodeField.vue'));
4. 将组件整合到页面当中;

现在我们就可以在程序到需要使用到该组件的地方,引入该组件就可以:

<form>
    <send-code-field></send-code-field>
</form>

Conclusion!

把一些复杂的页面操作拆分成单独的、可复用、可定制样式(基于 Bootstrap)的 Vue 组件,慢慢的形成团队内部的代码库(当然也欢迎开源出来),灵活的搭配使用,会让开发效率和维护工作变的非常简单。

而且这样处理之后 Vue 组件不仅可以在单页面应用中使用,而且在传统的 Web 应用也可以灵活的复用。我最近还尝试使用 Vue 组件和 Framework7 去搭建 Hypid 应用,也是一个不错的选择。

PS

这篇文章其实做成视频可能更好一点,这么长的文章估计也没多少人看,:smile: 不知道写 Laravel 项目的时候会考虑使用 Vue 组件吗 :question:

Remote. Open. Engineer.
本帖已被设为精华帖!
本帖由系统于 7年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 34
前端猫哥

不错 通俗易懂,正好需要

7年前 评论
chongyi

不错 ~

7年前 评论
chenyuanqi

其实还好,比这个长多的文章多了去了,
如果连这样的文字都不读不下去的话,很难想象其技术的深度,所以放心去写吧

7年前 评论

一点都不长,写的很流畅:+1:

7年前 评论
lijinma

很流畅,点赞,很好的 Tutorial

7年前 评论
lijinma

@Macken 一般大一点的项目,一个 Component 里面的 template 和 js, css 都会分开吧。

没用 vue 做过大项目。

7年前 评论
Lonexw

@lijinma 我是觉得即使大(业务复杂、页面较多)一点的项目,维护一个基本、全局的项目样式库(例如:default.css), 单独的组件在样式库基础之上的定制代码因为和业务组件耦合性比较强,完全可以使用单文件系统管理更好一点。

7年前 评论
lijinma

@JobsLong 恩恩,你说得有道理,学习了。

7年前 评论

很好,不算长。讲的很通俗易懂

7年前 评论

謝謝分享 學到了!

7年前 评论

现在正在看Vue的东西,但是laravel 5.3是在composer安装之后就默认引入了Vue组件还是需要手动引入

7年前 评论
meitesi

@Heroic 默认就引入了

7年前 评论

单app.js入口的话,假设项目有很多组件最终构建出来的app.js岂不很庞大吗,有好点的解决办法吗?

7年前 评论
Lonexw

@史沟飞

It depends on the project.

对于很多项目来说,单文件是非常有优势的,不论是对于开发流程来说,还是用户使用来说,很简化,用户端进行的请求更少。

对于体量更大的项目(也怎么没接触过),其实我的思路是根据业务划分,或者在应用层进行切割、划分更好处理。

发现了 Yahoo 的一篇关于前端性能的文章 Best Practices for Speeding Up Your Web Site,先贴出来,我晚上也去看看~~

7年前 评论

您好,请教您一个问题,我在laravel框架中使用官网是那个example示例,一直没有任何反应,页面的标签一直都不变化,看您的示例也解决不了问题,冒昧在这里问一下,希望您能给我一些指导,感激不尽。谢谢!!!

7年前 评论
Lonexw

@语言不能表达一切 我觉得你是没有运行搭建 node 环境,开发的时候运行:

gulp watch 

是吧?

7年前 评论

有一个新的问题,请教一下,gulp生成的app.js大小有2.9M,一个简单示例就这么大,有点难以接受,请教一下您,这个生成的文件,是天生就这么大,还是我的操作哪里出了问题?

7年前 评论
Lonexw

@语言不能表达一切 应该不会吧,你有修改过 gulpfile.js 的任务吗?

7年前 评论

刚从公司回宿舍。gulpfile.js这个文件我没改过。希望有人做例子的时候,帮忙给看看。刚又重新开了一个laravel项目,还是2.9M多。我看了项目public\js\app.js的这个文件的内容,里边也确实有好多东西,像bootstrap、vue的代码都包含在里边。还有其他一些不认识的。大有用一个app.js文件将页面用到的所有js都包含进去的意思。

7年前 评论
Lonexw

@语言不能表达一切 正常也就 200 多 KB :neutral_face: , 没有碰到过这种情况

7年前 评论

@语言不能表达一切 使用 gulp --production,压缩后一般在 300k 左右。

7年前 评论

你好,我看你使用post请求发送数据,那么csrf_field这个input在哪儿设置呢?

7年前 评论
颜⑧

好文 :100:

7年前 评论

安装的时候出现以下错误,有没有人遇到过呀。

  • Can only install one of: jeremeamia/SuperClosure[2.2.0, 2.3.0].
  • Can only install one of: jeremeamia/SuperClosure[2.3.0, 2.2.0].
  • Can only install one of: jeremeamia/SuperClosure[2.2.0, 2.3.0].
  • toplan/phpsms dev-master requires jeremeamia/superclosure ~2.2.0 -> satisfiable by jeremeamia/SuperClosure[2.2.0].
  • Installation request for toplan/phpsms dev-master -> satisfiable by toplan/phpsms[dev-master].
  • Installation request for jeremeamia/superclosure (locked at 2.3.0) -> satisfiable by jeremeamia/SuperClosure[2.3.0].
7年前 评论

@Lv007 补充一下是安装 PhoSms

7年前 评论

公司项目需要做多页面,而且需要做SEO,这套估计gg了。引入了blade模板貌似可以解决多页的问题,然而,要在服务端把组件渲染成字符串就有点蛋疼了

5年前 评论
ruodee

顶,这个不错。

4年前 评论
elesos

不错的教程

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!