3

从本节开始,将学习搭建一个问答模块的站点,先开始我们的注册,登录,邮箱验证,信息提示。

一、邮件扩展包Sendcould

项目中,我们使用Sendcloud作为我们的邮件代理服务器。
安装sendcould

$ composer require naux/sendcloud
$ composer require guzzlehttp/guzzle

sendcloud详细用法请访问Github: naux/sendcloud

修改 config/app.php,添加服务提供者

'providers' => [
   // 添加这行
    Naux\Mail\SendCloudServiceProvider::class,
];

.env 中配置你的密钥, 并修改邮件驱动为 sendcloud

MAIL_DRIVER=sendcloud

SEND_CLOUD_USER=   # 创建的 api_user
SEND_CLOUD_KEY=    # 分配的 api_key

二、命令自动生成注册登录模块

vagrant@homestead:~/Code/my-app$ php artisan make:auth

clipboard.png

laravel5.4 为我们带了开箱即用的用户注册系统,只需一个操作命令,即可完成登录模块的创建。

三、信息提示

GitHub安装开源的信息提示包laracasts/flash

composer require laracasts/flash

And then, if using Laravel 5, include the service provider within config/app.php.

'providers' => [
    Laracasts\Flash\FlashServiceProvider::class,
];

laracasts/flash详细用法请访问Github: laracasts/flash

四、多语言翻译包

clipboard.png

我们可以看到,提示的信息为英文,我们想转为中文,应该怎样做呢?
GitHub 上有人专门为此写了一个扩展包 - Laravel-lang来对 Laravel 提供默认提示信息添加多语言版本翻译。

接下来让我们使用Composer来安装 Laravel-lang

$ composer require "caouecs/laravel-lang:~3.0"

安装后的 laravel-lang 扩展包的所有核心文件都将被放置在 vendor/ 文件夹下,其中包括我们需要的中文语言包,让我们将中文语言包提取到 Laravel 默认指定的语言包存放路径 resources/lang 中。

$ cp -a vendor/caouecs/laravel-lang/src/zh-CN resources/lang

完成之后你便可在 resources/lang/zh-CN 文件夹中看到我们新增的语言包文件。

最后,我们还需要将项目语言设置为中文。
config/app.php

<?php

return [
    .
    .
    .
    'locale' => 'zh-CN',
    .
    .
    .
];   

现在再次提交验证不通过的信息,能看到错误提示已变成中文。

clipboard.png

Laravel-lang 详细用法请访问 Github:Laravel-lang

五、laravel-ueditor编辑器

项目中我们使用安正超同学开源的overtrue/laravel-ueditor编辑器进行开发。
使用方法:

安装

$ composer require "overtrue/laravel-ueditor:~1.0"

配置

添加下面一行到 config/app.phpproviders 部分:

Overtrue\LaravelUEditor\UEditorServiceProvider::class,

发布配置文件与资源

$ php artisan vendor:publish

模板引入编辑器

这行的作用是引入编辑器需要的 css,js 等文件,所以你不需要再手动去引入它们。

@include('vendor.ueditor.assets')

编辑器的初始化

<!-- 实例化编辑器 -->
<script type="text/javascript">
    var ue = UE.getEditor('container');
    ue.ready(function() {
        ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
    });
</script>

<!-- 编辑器容器 -->
<script id="container" name="content" type="text/plain"></script>

laravel-ueditor详细用法请访问Github: overtrue/laravel-ueditor

六、select2选择框使用

很好用的一款选择框组件,详细用法请看Select2官网示例。

clipboard.png

CDN:

<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>

用法:

<script type="text/javascript">
  // 初始化
  $('select').select2();
</script>

简单示例:


<html>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.static.runoob.com/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>

<select class="js-example-basic-multiple" multiple="multiple">
  <option value="AL">Alabama</option>
  <option value="WY">Wyoming</option>
</select>

<script type="text/javascript">

    $(".js-example-basic-multiple").select2();

</script>


</html>

或者我们可以将上边的CDN资源文件通过curl -O命令下载到本地项目目录中:

cd resources/assets/sass/
mk css
~/Code/zhihu-app/resources/assets/sass/css$ curl -O https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css

// 下载js文件
~/Code/zhihu-app/resources/assets/js$ curl -O https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js

实例应用

laravel5.4 中应用 select2 插件:
select2.min.cssselect2.min.js文件经过 gulp编译后生成app.cssapp.js打包后的文件,然后在总视图布局文件/layouts/app.blade.php中应用,

<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Styles -->
    <link href="{{ elixir('css/app.css') }}" rel="stylesheet">

    <!-- Scripts -->
    <script>
        window.Laravel = {!! json_encode([
            'csrfToken' => csrf_token(),
        ]) !!};
    </script>
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-default navbar-static-top">
            <div class="container">
                <div class="navbar-header">

                    <!-- Collapsed Hamburger -->
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
                        <span class="sr-only">Toggle Navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>

                    <!-- Branding Image -->
                    <a class="navbar-brand" href="{{ url('/') }}">
                        {{ config('app.name', 'Laravel') }}
                    </a>
                </div>

                <div class="collapse navbar-collapse" id="app-navbar-collapse">
                    <!-- Left Side Of Navbar -->
                    <ul class="nav navbar-nav">
                        &nbsp;
                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="nav navbar-nav navbar-right">
                        <!-- Authentication Links -->
                        @if (Auth::guest())
                            <li><a href="{{ route('login') }}">Login</a></li>
                            <li><a href="{{ route('register') }}">Register</a></li>
                        @else
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <ul class="dropdown-menu" role="menu">
                                    <li>
                                        <a href="{{ route('logout') }}"
                                            onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                            Logout
                                        </a>

                                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                            {{ csrf_field() }}
                                        </form>
                                    </li>
                                </ul>
                            </li>
                        @endif
                    </ul>
                </div>
            </div>
        </nav>
        @include('shared.messages')
        @yield('content')
    </div>

    <!-- Scripts -->
    <script src="{{ elixir('js/app.js') }}"></script>
    @yield('js')
    <script>
        $('#flash-overlay-modal').modal();
    </script>
</body>
</html>

在底部引用app.js代码,并添加区块布局 yield('js'),在继承该总布局页面时,有关应用js代码的需要放在 @section('js') 如select2 @endsection中,如下面的这个子页面 create.blade.php

@extends('layouts.app')

@section('content')
    @include('vendor.ueditor.assets')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">发布问题</div>

                    <div class="panel-body">
                        @include("shared.errors")
                        <form action="/questions" method="post">
                            {{ csrf_field() }}
                            <div class="form-group">
                                <label for="title">标题</label>
                                <input type="text" name="title" value="{{ old('title') }}" class="form-control" placeholder="标题" id="title">
                            </div>

                            <div class="form-group">
                                <select class="js-example-basic-multiple  form-control" multiple="multiple">
                                    <option value="AL">Alabama</option>
                                    <option value="WY">Wyoming</option>
                                </select>
                            </div>

                            <!-- 编辑器容器 -->
                            <label for="title">内容</label>
                            <script id="container" name="body" style="height:200px" type="text/plain">
                                {!! old('body') !!}
                            </script>
                            <button class="btn btn-success pull-right" type="submit">发布问题</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 实例化编辑器 -->
    @section('js')
    <script type="text/javascript">
        var ue = UE.getEditor('container', {
            toolbars: [
                ['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft','justifycenter', 'justifyright',  'link', 'insertimage', 'fullscreen']
            ],
            elementPathEnabled: false,
            enableContextMenu: false,
            autoClearEmptyNode:true,
            wordCount:false,
            imagePopup:false,
            autotypeset:{ indent: true,imageBlockLine: 'center' }
        });
        ue.ready(function() {
            ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
        });

        // select2,如果没有预加载ready,否则不会出现
        $(document).ready(function () {
            $(".js-example-basic-multiple").select2();
        });
    </script>
    @endsection

@endsection

clipboard.png

七、axios HTTP请求包

Promise based HTTP client for the browser and node.js

Github地址:mzabriskie/axios

安装:

$ npm install axios

laravel5.4开始使用 axios做http请求,如果用旧的请求方法,会报这样的错误:

Uncaught TypeError: Cannot read property 'post' of undefined

clipboard.png

具体的代码:

export default {
    props:['question', 'user'],
    mounted() {

       /**  这种旧的写法会在Laravel5.4中报错
        this.$http.post('/api/question/follower', {'question':this.question, 'user':this.user}).then(response => {
            console.log(response.data);
        })
        */
       axios.post('/api/question/follower', {
           'question':this.question,
           'user':this.user
       }).then(function(response){
           console.log(response.data);
       })
    },

八、vue图片剪裁上传组件

vue图片剪裁上传组件: vue图片剪裁上传组件

1.npm 安装

npm install vue-image-crop-upload

// ES6 依赖
npm install babel-polyfill      

2.用法

Example vue@2示例:

<div id="app">
    <a class="btn" @click="toggleShow">set avatar</a>
    <my-upload field="img"
        @crop-success="cropSuccess"
        @crop-upload-success="cropUploadSuccess"
        @crop-upload-fail="cropUploadFail"
        v-model="show"
        :width="300"
        :height="300"
        url="/upload"
        :params="params"
        :headers="headers"
        img-format="png"></my-upload>
    <img :src="imgDataUrl">
</div>

<script>
    import 'babel-polyfill'; // es6 shim
    import Vue from 'vue';
    import myUpload from 'vue-image-crop-upload/upload-2.vue';

    new Vue({
        el: '#app',
        data: {
            show: true,
            params: {
                token: '123456798',
                name: 'avatar'
            },
            headers: {
                smail: '*_~'
            },
            imgDataUrl: '' // the datebase64 url of created image
        },
        components: {
            'my-upload': myUpload
        },
        methods: {
            toggleShow() {
                this.show = !this.show;
            },
            /**
             * crop success
             *
             * [param] imgDataUrl
             * [param] field
             */
            cropSuccess(imgDataUrl, field){
                console.log('-------- crop success --------');
                this.imgDataUrl = imgDataUrl;
            },
            /**
             * upload success
             *
             * [param] jsonData  server api return data, already json encode
             * [param] field
             */
            cropUploadSuccess(jsonData, field){
                console.log('-------- upload success --------');
                console.log(jsonData);
                console.log('field: ' + field);
            },
            /**
             * upload fail
             *
             * [param] status    server api return error status, like 500
             * [param] field
             */
            cropUploadFail(status, field){
                console.log('-------- upload fail --------');
                console.log(status);
                console.log('field: ' + field);
            }
        }
    });
</script>

将上边的代码应用到组件 Avatar.vue 中,需要对相关的方法按照组件的要求改一改,data 需要使用函数 return 进行返回。

<template>
    <div style="text-align:center;">
    <my-upload field="img"
    @crop-success="cropSuccess"
    @crop-upload-success="cropUploadSuccess"
    @crop-upload-fail="cropUploadFail"
    v-model="show"
    :width="300"
    :height="300"
    url="/avatar"
    :params="params"
    :headers="headers"
    img-format="png"></my-upload>
        <img :src="imgDataUrl" style="width:80px;">
        <div style="margin-top:20px;">
          <button class="btn btn-default" @click="toggleShow">修改头像</button>
        </div>
    </div>
</template>

<script>
import 'babel-polyfill'; // es6 shim
import myUpload from 'vue-image-crop-upload/upload-2.vue';

export default {
    props:['avatar'],
    data() {
       return {
            show: false,
            params: {
                _token:Laravel.csrfToken,
                name: 'img',

                },
            headers: {
                smail: '*_~'
                },
            imgDataUrl: this.avatar // the datebase64 url of created image
        }
    },
    components: {
        'my-upload': myUpload
    },
    methods: {
        toggleShow() {
            this.show = !this.show;
        },
        /**
         * crop success
         *
         * [param] imgDataUrl
         * [param] field
         */
        cropSuccess(imgDataUrl, field){
            console.log('-------- crop success --------');
            this.imgDataUrl = imgDataUrl;
        },
        /**
         * upload success
         *
         * [param] jsonData  server api return data, already json encode
         * [param] field
         */
        cropUploadSuccess(response, field){
            console.log('-------- upload success --------');
           this.imgDataUrl = response.url;

            // 上传成功后,影藏掉
            this.toggleShow();
        },
        /**
         * upload fail
         *
         * [param] status    server api return error status, like 500
         * [param] field
         */
        cropUploadFail(status, field){
            console.log('-------- upload fail --------');
            console.log(status);
            console.log('field: ' + field);
        }
    }
}
</script>

Avatar.vue 引入app.js文件中

// 设置头像
Vue.component('avatar', require('./components/Avatar.vue'));

const app = new Vue({
    el: '#app'
});

在视图文件 avatar.blade.php 中使用 avatar 组件

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">更换头像</div>

                    <div class="panel-body">

                    <avatar avatar="{{ Auth::user()->avatar }}"></avatar>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

服务器的保存控制器UsersController.php方法:

   /**
     * 头像上传保存到本地服务器
     */
    public function avatarUpload(Request $request)
    {
        // 获取图片文件对象
        $file = $request->file('img');

        // 文件名
        $filename = md5(time() . user()->id) . '.' . $file->getClientOriginalExtension();

        $file->move(public_path('avatars'), $filename);

        // 修改用户的头像
        // user()->avatar = asset(public_path('avatars/'.$filename));
        user()->avatar = '/avatars/'.$filename; // 相对路径
        user()->save();

        return ['url' => user()->avatar];
    }

Github地址:https://github.com/dai-siki/v...

九、图片上传到七牛云

七牛云扩展包GitHub地址:laravel-filesystem-qiniu

1.安装

$ composer require "overtrue/laravel-filesystem-qiniu"

2.配置

config/app.php 文件中配置:

'providers' => [
    // Other service providers...
    Overtrue\LaravelFilesystem\Qiniu\QiniuStorageServiceProvider::class,
],

config/filesystems.php 中配置:

 'qiniu' => [
       'driver'     => 'qiniu',
       'access_key' => env('QINIU_ACCESS_KEY', 'xxxxxxxxxxxxxxxx'),
       'secret_key' => env('QINIU_SECRET_KEY', 'xxxxxxxxxxxxxxxx'),
       'bucket'     => env('QINIU_BUCKET', 'test'),
       'domain'     => env('QINIU_DOMAIN', 'xxx.clouddn.com'), // or host: https://xxxx.clouddn.com
    ],

3.具体用法:

 /**
     * 头像上传保存到本地服务器
     */
    public function avatarUpload(Request $request)
    {
        // 获取图片文件对象
        $file = $request->file('img');

        /*
         * 本地存储图片
        // 文件名
        $filename = md5(time().user()->id) . '.' . $file->getClientOriginalExtension();

        // 将图片保存到本地
        // $file->move(public_path('avatars'), $filename);

        // 修改用户的头像
        // user()->avatar = asset(public_path('avatars/'.$filename));
        user()->avatar = '/avatars/'.$filename; // 相对路径
        */

        // 将图片保存到七牛[20170405]
        $filename = 'avatars/' . md5(time().user()->id) . '.' . $file->getClientOriginalExtension();

        Storage::disk('qiniu')->writeStream($filename, fopen($file->getRealPath(), 'r'));
        user()->avatar = 'http://'.config('filesystems.disks.qiniu.domain') . '/' . $filename;

        user()->save();

        return ['url' => user()->avatar];
    }

重构提交数据的验证

我们需要对客户端提交的数据进行验证,验证通过后才能入库,如下边的发表一篇文章为例,通常的写法是在store方法中获取提交的数据,之后再通过规则验证valigate,不过这里我们有一个更好的方法,那就是使用依赖注入

  /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // $answer = $request->all();
        // dd($answer);

        // 验证提交的数据
        $rules = [
            'title' => 'required|min:6|max:150',
            'body'  => 'required|min:40'
        ];

        // 自定义消息提示
        $messages = [
            'body.required' => "内容 不能为空。",
            'body.min' => "内容 不能少于40个字符。",
        ];
        $this->validate($request, $rules, $messages);

        $data = [
            'title' => $request->get('title'),
            'body' => $request->get('body'),
            'user_id' => Auth::id(),
        ];
        $question = Question::create($data);

        flash("恭喜你,发布成功!", "success");
        return redirect()->route('questions.show', [$question->id]);
    }

上边的方法,是将验证规则写在store方法里边的,我们可以通过依赖注入对上边的方法进行重构,先使用命令生成request。

php artisan make:request StoreQuestionRequest

将表单的验证写入用命令生成的Http\Requests\StoreQuestionRequest.php文件

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreQuestionRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Valigate messages
     *
     * @return array
     */
    public function messages()
    {
        return [
            'body.required' => "内容 不能为空。",
            'body.min'      => "内容 不能少于40个字符。",
        ];
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|min:6|max:150',
            'body'  => 'required|min:40'
        ];
    }
}

将表单验证注入到接收的参数:

 /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreQuestionRequest $request)
    {
      
        // $this->validate($request, $rules, $messages);

        $data = [
            'title' => $request->get('title'),
            'body' => $request->get('body'),
            'user_id' => Auth::id(),
        ];
        $question = Question::create($data);

        flash("恭喜你,发布成功!", "success");
        return redirect()->route('questions.show', [$question->id]);
    }

这个方法里边的参数store(StoreQuestionRequest $request)即可对传递过来的参数进行验证,重构之后,我们的store方法是不是简洁了许多^_^


Corwien
6.3k 声望1.6k 粉丝

为者常成,行者常至。