不做伸手党,分享快乐,Laravel 5 入门小项目详细教程

仅献给刚入门的同学,又找不到好的项目练习的同学,社区里面的大牛请绕道 “^_^”

这里感谢论坛,社区那些无私的大牛们

github地址

可能会有改动,可以关注github

StuGradeWithLaravel5

一个简易的学生成绩管理,查询系统,仅供新手学习参考。QQ:215672398 有什么好的想法,或是建议,不明白的都可以来找我讨论。

下面,我们开始,我也是从一个新手(伸手党)走过来的,这次自己写一个教程给刚学习laravel,或者感到困惑的同学,我会尽量详细每一个重要的步骤。感谢社区,论坛前仆后继的前辈,本教程仅献给像我一样热爱laravel的新手参考学习,谢谢!

功能

  • 学生端
    • 登录完善修改个人信息,查看成绩
    • 修改密码,验证邮件(待完善)
  • 管理员
    • 登录能查看所有学生信息,包括新增学生,删除学生
    • 能更新学生成绩,没有修改学生信息的权限
    • 下载学生信息Excel,学生成绩Excel
    • 上传学生名单Excel,更新数据库(待完善)
    • 上传学生成绩Excel,更新数据库(待完善)

组件链接和文档

  • Laravel 5.0(这个大家都懂,最基本的)
  • Bootstrap 3.3.4
  • gulp,前端自动化工具,(官网被墙了),推荐laravel文档中的安装gulp. link
  • TableSorter 表格排序插件
  • Laravel Excel Excel的导出导入组件

说明

  • TableSorter ----- resources/js/jquery.tablesorter.min.js
  • Bootstarp ----- 需要引入bootstarp的font文件(不引入会出现一些问题,见下文)
  • resources/js/main.js ----- 系统功能完善js脚本(意思就是自己写的)
  • resources/css/main/css ----- 同上

环境需要

PHP >= 5.5.0
MCrypt PHP Extension
SQL server(for example MySQL)

安装步骤


1: 下载源代码

https://github.com/qq215672398/StuGradeWithLaravel5/archive/master.zip

2: Composer Install

安装Composer 来管理PHP依赖关系的工具,详细安装参考官网,不过大部分同学这步肯定Ok了。

安装好之后,在项目根目录下执行命令:

composer update

3: 修改配置文件

将.env.example重命名为.env文件中并且修改

DB_HOST=localhost
DB_DATABASE=dbname
DB_USERNAME=username
DB_PASSWORD=password

在config/app.php 下修改:

'url' => 'http://localhost/项目文件夹名/public',
'timezone' => 'PRC',

4: 安装gulp和Elixir (安装阶段可以跳过)

执行以下命令:

npm install

5: 得到表,和默认用户数据

依次执行一下命令:

php artisan migrate
php artisan db:seed(要是失败的话,可能需要 composer dump-autoload)

现在执行:

php artisan serve

打开浏览器,输入http://localhost:8000/就可以看到首页呢!


6: 登录开始

数据库有3个默认用户(当然你可以在数据库里删除,自己添加,这里只是测试)
这里的相关信息可以在 /database/seeds/UserTableSeeder.php 找到

管理员:

username: 1234567890
pasword: root

同学:

username: 1210311232
pasword: 1210311232

username: 1210311233
pasword: 1210311233

开始学习(至少要阅读过一遍官方文档,熟悉基本用法)

首先,讲讲的我自己的学习思路,拿到一个应用的源代码,我会先看路由,每一个路由对应着什么功能,其中有什么细节,怎么去实现,这是我最希望能学到的。在编码的时候,我个人是路由-功能去完成的,可能这是新手的方法,有大牛能指导一下能更好。所以,我从逐步从路由开始说起,一步一步的编码过程。

你可能会遇到的问题

  • Class App\Http\Controllers\Admin\ExcelController does not exist
  • Class 'App\Http\Controllers\Admin\User' not found
  • 类似的问题

你需要:

  • 检查文件名,看是否一致
  • 检查命名空间
  • 检查类名
  • 检查是否在类外 use User 或者 use Auth 等等

建议,开两个编辑器,一个用来看前面安装好的源代码,一个用来进行下面的学习,学习过程中,请随便参考官方文档,另外,我的教程里面可能不小心会有些小错误,请耐心查看,有些你已经明白了,但是实际操作出现错误无法解决的,你可以按照我下面说的循序,复制原项目的文件,也可以找我交流,谢谢!

安装好laravel,配置好环境之后,我执行了

php artisan fresh

只留下了laravel自带的主页路由 Route::get('/', 'WelcomeController@index');

然后导入我们的数据表,填充默认数据进行测试。

打开 /database/migrations/._create_users_table.php,修改up方法

public function up()
{
    Schema::create('users', function(Blueprint $table)
    {
        $table->integer('id')->unique()->unsigned();  #学号,唯一,取正数
        $table->string('name');                       #姓名
        $table->string('password');                   #密码
        $table->string('phone')->default('');  #手机 默认为空(不是可以为空,值为'')
        $table->string('sex')->default('');    #性别 同上
        $table->string('email')->default('');  #邮箱 同上
        $table->string('pro_class')->default(''); #班级 同上
        $table->boolean('is_admin')->default(0);  #是否为管理员 默认为学生
        $table->rememberToken();
        $table->timestamps();
    });
}

这里为什么这么写,首先我觉的老师新增学生时候是没有填写他信息的权限的,只能生成学号,姓名。密码,所以其他都默认为空,需要学生登录后自己去填写。

然后在User.php中修改$fillable数组

protected $fillable = ['name', 'email', 'is_admin', 'password', 'sex', 'phone', 'pro_class'];

什么意思,我也不是很清楚,这样写了储存user到数据库,输出user属性就不会出问题,至于具体为啥,怪我没有深入,下面继续。

我们先先数据库里面填充一些数据,以方便我们下面测试.

在/database/seeds下新建 UserTableSeeder.php

<?php

use Illuminate\Database\Seeder;
use App\User;
use App\Grade;

class UserTableSeeder extends Seeder {

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->delete();

        User::create([
        'id' => 1210311232,
        'name' => '李锐',
        'password' => Hash::make('1210311232')
        ]);

        User::create([
        'id' => 1210311233,
        'name' => '陈曦',
        'password' => Hash::make('1210311233')
        ]);

        User::create([
        'id' => 1234567890,
        'name' => '管理员',
        'password' => Hash::make('root'),
        'is_admin' => 1
        ]);

    }

}

然后讲DatabaseSeeder.php中 $this->call('UserTableSeeder')前面的注释取消.

执行

composer dump-autoload
php artisan db:seed

数据就被填充到数据库里面了

下面开始有任何不确定的都可以去看源代码对比

找到WelcomeController.php,可以看到两段代码:

public function __construct()
{
    $this->middleware('guest');
}

public function index()
{
    return view('welcome');
}

上面的构造函数,有什么作用?里面的中间件guest,在Kernel.php 中的routeMiddleware数组里面有注册,它的功能在App\Http\Middleware\RedirectIfAuthenticated.php里面可以看到。

可以理解为登录之后要是还想访问主页,就会自动跳转,跳转细节后面在说。

index方法返回的是welcome页面.

这时候,我们先构建一个基础页面模版,因为后面的每个页面都是需要继承它的.创建 master.blade.php文件.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title> @yield('title') </title>
    <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<body>
    <nav class="navbar navbar-default">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                    <span class="sr-only">Toggle Navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @if(Auth::guest())
                    <a class="navbar-brand" href="/">学生成绩管理</a>
                @else
                    @if (Auth::user()->is_admin)
                        <a class="navbar-brand" href="/admin">学生成绩管理</a>
                    @else
                        <a class="navbar-brand" href="/">学生成绩管理</a>
                    @endif
                @endif

            </div>

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li><a href="http://www.golaravel.com" target="__blank">Power by laravel5</a></li>
            </ul>
                <ul class="nav navbar-nav navbar-right">
                    @if (Auth::guest())
                    @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="{{ url('/logout') }}">退出</a></li>
                            </ul>
                        </li>
                    @endif
                </ul>
            </div>
        </div>
    </nav>

    <!-- <div class="container">
        @include('flash')
    </div> -->

    @yield('content')

<!-- script -->
<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
</body>
</html>

@include('flash')先注释掉,这里暂时还不需要.(记得注释,你可以先删除,最好)

创建我们的welcome.blade.php,随便修饰一下(前端不行 :) )

@extends('master')   {{-- 继承master模版 --}}

@section('title')   {{-- 对应@yield('title') --}}
    学生成绩管理系统
@stop

@section('content')   {{-- 对应@yield('content') --}}
    <div class="container">
        <div class="jumbotron">
            <h2><div class="quote">{{ Inspiring::quote() }}</div></h2>
            <p>同学们登录后先修改相关资料</p>
            <p>查询分数,有疑问咨询管理员</p>
            <p><a class="btn btn-primary btn-lg" href="/login" role="button">点击登录</a></p>
        </div>
    </div>
@stop

Index


好了,首页已经完成了,来看这三个路由

Route::get('login', [
'middleware' => 'guest', 'as' => 'login', 'uses' => 'loginController@loginGet']);
Route::post('login', [
'middleware' => 'guest', 'uses' => 'loginController@loginPost']);
Route::get('logout', [
'middleware' => 'auth', 'as' => 'logout', 'uses' => 'loginController@logout']);

完成登录登出的功能, 在路由中设置中间件, 过滤一些非法请求,关于中间件,参考官方文档

guest 只允许游客(没登陆的情况下)访问get路由login和post路由login,要是已经登录,就会跳转到相应页面,注意关键词响应。我们登录用户有两种,学生,和管理员,当他们在登录的情况下要想访问这两个路由,肯定会做出不同的响应。即,学生,跳转到学生主页,管理员,跳转到管理员主页.现在来看看RedirectIfAuthenticated.php

public function handle($request, Closure $next)
{
    if ($this->auth->check())     <!-- 用户是否登录 -->
    {
        if (!Auth::user()->is_admin) {
            return new RedirectResponse(url('/stu/home'));  <!-- 不是管理员 -->
        } else {
            return new RedirectResponse(url('/admin'));   <!-- 管理员 -->
        }

    }

    return $next($request);
}

auth 只有登录用户才能访问(这个不知道怎么表达,我就不误人子弟),看下源码 Authenticate.php

public function handle($request, Closure $next)
{
    if ($this->auth->guest())    <!-- 没有登录,是游客 -->
    {
        if ($request->ajax())           <!-- 通过ajax来请求 -->
        {
            return response('Unauthorized.', 401);
        }
        else       <!-- 直接请求, 跳转到登录页 -->
        {
            return redirect()->guest('login');
        }
    }

    return $next($request);
}

也就是说只有登录了才能登出,就是这个意思。

说完了中间件,创建控制器。loginControlle.php

php artisan make:controller loginController --plain

在里面写上以下内容:

/**
 * 返回login视图,登录页面
 */
public function loginGet()
{
    return view('login');
}

/**
 * 登录响应
 */
public function loginPost(Request $request)
{
    $this->validate($request, User::rules());
    $id = $request->get('id');
    $password = $request->get('password');
    if (Auth::attempt(['id' => $id, 'password' => $password], $request->get('remember'))) {
        if (!Auth::user()->is_admin) {
            return Redirect::route('stu_home');
        } else {
            return Redirect::action('Admin\AdminController@index');
        }

    } else {
        return Redirect::route('login')
            ->withInput()
            ->withErrors('学号或者密码不正确,请重试!');
    }
}

/**
 * 用户登出
 */
public function logout()
{
    if (Auth::check()) {
        Auth::logout();
    }
    return Redirect::route('login');
}

可以使用validate验证用户输入,在laravel5中使用validate非常方便,注意第二个参数,User::rules(),
这是在User模型中一个静态方法,接着在User.php中加入静态方法。

protected static function rules()
{
    return [
        'id' => 'required|digits:10',   <!-- 代表必需填写,10位数字 -->
        'password' => 'required'        <!-- 必填 -->
        ];
}

验证用户登录使用了Auth::attempt(),这是laravel中自带的验证方法,非常好用,如果验证通过,接着判断是否是管理员,然后分别跳转到不同的url.

return Redirect::route('stu_home') -- 对应路由名为stu_home的路由
return Redirect::action('Admin\AdminController@index') -- 对于这个index方法

登出使用的是Auth::logout().

这时候点击登录,laravel会告诉你view(login)不存在,创建login.blade.php文件

@extends('master')

@section('title')
    欢迎登录
@stop

@section('content')
<div class="container-fluid">
    <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('errors.list')

                    {!! Form::open(['url' => '/login', 'class' => 'form-horizontal', 'role' => 'form']) !!}
                        <div class="form-group">
                            {!! Form::label('id', '学号', ['class' => 'col-md-4 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::text('id', old('id'), ['class' => 'form-control', 'required']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('password', '密码', ['class' => 'col-md-4 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::password('password', ['class' => 'form-control', 'required']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <div class="checkbox">
                                    <label>
                                        <input type="checkbox" name="remember"> Remember Me
                                    </label>
                                </div>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                {!! Form::submit('Login', ['class' => 'btn btn-primary form-control']) !!}
                            </div>
                        </div>
                    {!! Form::close() !!}
                </div>
            </div>
        </div>
    </div>
</div>
@stop

这时候要是点击登录,还是会报错,因为在laravel5中Illuminate/Html组件被移了,我们可以在composer.json的required数组中加入

"illuminate/html": "5.0"

在config/app.php中的provider数组中添加

'Illuminate\Html\HtmlServiceProvider',

aliases数组中添加

'Html'      => 'Illuminate\Html\HtmlFacade',
'Form'      => 'Illuminate\Html\FormFacade',

接着执行:

composer update

等待安装完成之后,就能看到我们的登录页面了

Login

这时候你随便输入学号密码,页面会刷新一下,不会跳转,错误已经被存在了Session中,现在把他显示出来.
在login.blade.php中有这样一行

@include('errors.list')

我们创建errors/list.blade.php

@if (count($errors) > 0)
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

这时候要是输入不符合前面的规则,你会看到提示英文提示信息,要是输入密码或者帐号错误,你会看到 学号或者密码不正确,请重试, 这是loginPost中返回的自定义错误信息.

现在,我们有两个问题需要解决,第一,英文提示信息,对我们中国用户可能不太友好,你可以更换.第二,错误提示会一直留着页面.下面,我们一一解决.

关于表单验证的自定义错误信息,可以查看官方链接, 现在我们找到/resources/lang/en/validation.php,在custom数组中添加:

'id' => [
        'required' => '学号不能为空',
        "digits"   => "学号必须是 10 位数字",
        "unique"   => "该同学已经存在",
    ],
'password' => [
        'required' => '密码不能为空',
    ],

重新随便输入学号密码,你就可以看到中文提示信息了

关于提示信息的隐藏,这里有两种简单的解决方案,参考bootstrap中的警告框,修改/errors/list.blade.php

@if (count($errors) > 0)
    <div class="alert alert-danger alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

这个时候错误信息的右边就会有一个关闭按钮,点击就可隐藏错误信息

还有一种使用jQuery的一个小方法.在master.blade.php引入脚本main.js

...
<!-- script -->
<script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
...

在public文件夹下新建文件 js/main.js

$(function () {
    $('div.alert').delay(2500).slideUp(300);
});

这是时候再产生错误信息,就会自动隐藏了

下面就是登录后的操作了,学生端的功能比较少,我们先来完成.

选择一组学号密码登录,你会看到url跳转到 http://localhost:8000/stu/home, 在前面loginController中loginPost方法中可以看到关于学生成功登录后的跳转,这时候去创建我们的路由.

Route::get('stu/home', [
    'as' => 'stu_home', 'uses' => 'Stu\StudentController@home']);
Route::get('stu/edit', [
    'as' => 'stu_edit', 'uses' => 'Stu\StudentController@edit']);
Route::post('stu/update', [
    'as' => 'stu_update', 'uses' => 'Stu\StudentController@update']);

接着创建我们的控制器(注意路径, 在Stu下):

php artisan make:controller Stu/StudentController --plain

里面包括以上三个方法,我们一个一个来解决

/**
 * 只允许登录用户访问
 */
public function __construct()
{
    $this->middleware('auth');
}

/**
 * 返回学生主页
 */
public function home()
{
    return view('stu.home');
}

接下来去创建我们的视图文件stu/home.blade.php

@extends('master')

@section('title')
    欢迎 -- {{ Auth::user()->name }}
@stop

@section('content')

<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <a href="/stu/home"><button class="btn btn-info">个人信息</button></a>

                </div>

                <div class="panel-body">
                    <div class="personal-mes">
                        学号: {{ Auth::user()->id }}
                        <br />
                        姓名: {{ Auth::user()->name }}
                        <br />
                        性别: {{ Auth::user()->sex }}
                        <br />
                        手机: {{ Auth::user()->phone }}
                        <br />
                        班级: {{ Auth::user()->pro_class }}
                        <br />
                        邮箱: {{ Auth::user()->email }}
                        <hr />
                        <a href="/stu/edit"><button class="btn btn-primary">修改资料</button></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@stop

刷新,你能看到自己的home页, 留意上面的代码,有一片空白,用来后面完成查分功能

stu_home

接着我们完成修改资料功能,在StudentController.php中添加:

/**
 * 返回修改资料页面
 */
public function edit()
{
    return view('stu.edit');
}

创建 stu/edit.blade.php

@extends('master')

@section('title')
    修改个人信息
@stop

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <a href="/stu/home"><button class="btn btn-info">个人信息</button></a>
                </div>

                @include('errors.list')

                <div class="panel-body">
                    {!! Form::open(['url' => '/stu/update', 'class' => 'form-horizontal', 'role' => 'form']) !!}
                        <div class="form-group">
                            {!! Form::label('id', '学号: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::text('id', Auth::user()->id, ['class' => 'form-control', 'readonly'])!!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('name', '姓名: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::text('name', Auth::user()->name, ['class' => 'form-control', 'readonly'])!!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('sex', '性别: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::select('sex', ['男' => '男', '女' => '女'], Auth::user()->sex, ['class' => 'form-control']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('phone', '手机: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::text('phone', Auth::user()->phone, ['class' => 'form-control']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('pro_class', '班级: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::text('pro_class', Auth::user()->pro_class, ['class' => 'form-control']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('email', '邮箱: ', ['class' => 'col-md-2 control-label']) !!}
                            <div class="col-md-6">
                                {!! Form::email('email', Auth::user()->email, ['class' => 'form-control']) !!}
                            </div>
                        </div>
                        <div class="group">
                            {!! Form::submit('确认修改', ['class' => 'btn btn-success form-control']) !!}
                        </div>
                    {!! Form::close() !!}
                </div>
            </div>
        </div>
    </div>
</div>
@stop

点击修改资料:

stu_edit

我们可以看到表单提交到 localhost:8000/stu/update,我们的post路由update对应方法如下:

public function update(Request $request)
{
    $this->validate($request, [
        'phone' => 'required|digits:11',
        'pro_class' => 'required',
        'email' => 'required|email'
        ]);

    Auth::user()->update($request->all());

    return Redirect::route('stu_home');
}

这个时候,我们也可以选择使用自己的Request,首先我们使用如下命令建立Request:

php artisan make:request StudentMesRequest

修改我们的StudentMesRequest.php:

/**
* Determine if the user is authorized to make this request.
* 这里先设置为true,表示有权限去使用这个Request,不然请求会被拒绝
* @return bool
*/
public function authorize()
{
    return true;
}

/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'phone' => 'required|digits:11',
        'pro_class' => 'required',
        'email' => 'required|email'
    ];
}

然后修改update方法(别忘了在类外引入StudentMesRequest, 即use App\Http\Requests\StudentMesRequest;):

public function update(StudentMesRequest $request)
{
    Auth::user()->update($request->all());

    return Redirect::route('stu_home');
}

以上两种方法都可以完成验证功能(看自己喜好吧,使用后者代码比较简洁,模块化,前面的登录也可以这样来写,你可以跳转回去试试),如果现在乱填表单,我们依旧可以看到错误提示,而且错误提示会自动消失,因为我们在edit.blade.php中添加了 @include('errors.list')

如果填写规范,提交后返回到stu_home页.这个时候我们可以添加一个成功信息,来增强用户体验

在 Auth::user()->update($request->all()) 添加:

session()->flash('message', '个人信息修改成功');

接着我们去让session信息读取出来,在master.blade.php中添加

<div class="container">
    @include('flash')
</div>

前面有提到,相信你知道放在哪儿比较合适.接着创建我们的flash.blade.php

@if (Session::has('message'))
    <div class="alert alert-success">
        {{ session('message') }}
    </div>
@endif

再次修改资料,成功后会看到提示信息,而且信息也会自动隐藏.

做了这么多,我还是提醒大家,随时随地查阅官方文档,或者对比我的代码,以免留下隐患

最后,我们来完成学生端的查分功能,首先,我们知道,users表里面是没有成绩字段的,所以我们这个时候需要建立第二个模型,Grade模型. let's do that

php artisan make:model Grade

找到Grade.php,添加:

protected $table = 'grades';

protected $fillable = [
    'math',     #高数
    'english',  #英语
    'c',        #c语言
    'sport',    #体育
    'think',    #思修
    'soft',     #软件工程
];

protected static function rules(){
    return [
        'math' => 'digits_between:0,2',
        'english' => 'digits_between:0,2',
        'c' => 'digits_between:01,2',
        'sport' => 'digits_between:1,2',
        'think' => 'digits_between:1,2',
        'soft' => 'digits_between:1,2',
        ];
}

有了前面User模型的基础,相信不难理解以上代码.接着在 /database/migrations/ 下找到 .._create_grades_table.php,修改文件:

public function up()
{
    Schema::create('grades', function(Blueprint $table)
    {
        $table->increments('id');
        $table->integer('user_id')->unsigned()->unique()->index();
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        $table->integer('math')->nullable();
        $table->integer('english')->nullable();
        $table->integer('c')->nullable();
        $table->integer('sport')->nullable();
        $table->integer('think')->nullable();
        $table->integer('soft')->nullable();
        $table->timestamps();
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::drop('grades');
}

这里有一点要说的,关于外键,我很多时候创建外键都不是一次成功的,我感觉我每次在这里都会出点问题,所以,大家要是创建成功,一定要有耐心,后面在phpMyAdmin里查看数据表会有惊喜,留给你去发现(我就不多说了),总之,这里不能跳过,对照源码,查阅资料,花点时间.

因为我们现在还没有完成后台的模块,所以不能进入后台上传分数,这里我们选择在seed里面填充数据
,打开UserTableSeeder.php,偷个懒,就不再创建一个seeder了,加入:

Grade::create([
    'user_id' => 1210311232,
    'math'    => 99,
    'english'    => 80,
    'c'    => 96,
    'sport'    => 95,
    'think'    => 99,
    'soft'    => 98,
    ]);

Grade::create([
    'user_id' => 1210311233,
    ]);

因为成绩是能为空的,所以像下面那样创建也是可以的,接着运行:

php artisan db:seed

这里我们需要在User.php里面建立和Grade一对一的模型关系,官方文档链接,link,想要更深入的理解Laravel Eloquent,请参考岁寒的三篇关于Eloquent的博文.

在User.php中添加:

public function grade()
{
    return $this->hasOne('App\Grade');
}

接着在试图里面去得到成绩,在前面所说的那个完成查询成绩的地方加上:

@include('stu.grade')

接着创建stu/grade.blade.php:

<button type="button" class="btn btn-warning"
data-container="body" data-toggle="popover" data-placement="bottom"
title="{{ Auth::user()->name }}--成绩"
data-content="
    ************** 高数 -- {{ $grade->math }} **************
    ************** 英语 -- {{ $grade->english }} **************
    ************ C语言 -- {{ $grade->c }} **************
    ************** 体育 -- {{ $grade->sport }} **************
    ************** 思修 -- {{ $grade->think }} **************
    ************** 软件 -- {{ $grade->soft }} **************
">
    点击,查看成绩
</button>

那么,这个$grade从何而来,我们回到StudentController,修改home方法:

$grade = Auth::user()->grade;

return view('stu.home', compact('grade'));

然后需要在我们的main.js里面添加这样一行:

$('[data-toggle="popover"]').popover();

这里可以参考bootstrap文档,弹出框

接着刷新我们的浏览器,点击查询分数,可以看到

stu_grade

下面,我们开始后台的模块

还是老样子,我们先退出当先登录用户,回到登录页面,登录管理员帐号

1234567890
root

看看我们的url跳转到 http://localhost:8000/admin

接着来添加我们的后台路由:

#查看成绩排名
Route::get('admin/grade', [
    'as' => 'grade_list', 'uses' => 'Admin\GradeController@index']);
#上传分数
Route::post('admin/upload_grade', [
    'as' => 'upload_grade', 'uses' => 'Admin\AdminController@upload_grade']);
#资源控制器,学生的增删改查
Route::resource('admin', 'Admin\AdminController');

首先来看看我们的资源控制器, 官方文档, 运行(注意路径):

php artisan make:controller Admin/AdminController

我们当前的url对应着index方法,进入AdminController.php:

public function __construct()
{
    $this->middleware('admin');
}

这是什么意思?先查看Kernel.php,我们向 $routeMiddleware 数组里面添加:

'admin' => 'App\Http\Middleware\isAdmin'

代表注册中间件 admin,接着运行如下命令新建isAdmin.php:

php artisan make:middleware isAdmin

进入到isAdmin.php:

public function handle($request, Closure $next)
{
    if (!Auth::check()) {
        return Redirect::route('login');
    } else {
        if (!Auth::user()->is_admin) {
            session()->flash('message_warning', '您不是管理员!无法进入相关区域');
            return Redirect::route('stu_home');
        }
    }
    return $next($request);
}

过滤没有登录的用户,重定向到登录页,过滤普通登录用户,重定向到学生登录首页,而且,返回一条警告信息,提示那是管理员区域.打开我们的flash.blade.php,增加:

@if (Session::has('message_warning'))
    <div class="alert alert-warning">
        {{ session('message_warning') }}
    </div>
@endif

现在来测试我们的中间件工作如何

  • 在url中输入http://localhost:8000/login,会发现无法跳转,现在你可以回过头看看之前路由中添加的中间件, 点击跳转

  • 在url中输入http://localhost:8000/logout,退出登录回到登录页

  • 输入一组学生帐号,然后在url中输入http://localhost:8000/admin,你会看到页面刷新,并且弹出警告信息.

warning

下面,继续查看AdminController中的index方法:

public function index()
{
    $result = User::where('is_admin', 0);
    $count = $result->count();
    $users = $result->paginate(10);
    return view('Admin.index', compact('users', 'count'));
}

$count,是学生数量,先传入,后面要使用,$users,这里我不知道怎么说,就是可以实现分页功能,参考官方文档,Link,代表我后面的视图文件中需要分页,每页显示10个user信息.接下来新建我们的视图文件 Admin/index.blade.php

@extends('master')

@section('title')
    管理员
@stop

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-10">

                @include('errors.list')

                <h3 align="center">学生信息表</h3>
                <table class="table table-hover">
                    <tr>
                        <td>学号</td>
                        <td>姓名</td>
                        <td>性别</td>
                        <td>手机</td>
                        <td>班级</td>
                        <td>邮箱</td>
                        <td>操作</td>
                    </tr>
                    @if (count($users))
                        @foreach ($users as $user)
                            <tr>
                                <td>{{ $user->id }}</td>
                                <td>{{ $user->name }}</td>
                                <td>{{ $user->sex }}</td>
                                <td>{{ $user->phone }}</td>
                                <td>{{ $user->pro_class }}</td>
                                <td>{{ $user->email }}</td>
                                <td>
                                    <button class="btn btn-sm btn-info" data-toggle="modal" data-target="#myModal{{$user->id}}">更新分数</button>
                                    <form action="{{ url('admin/'.$user->id) }}" style='display: inline' method="post">
                                        <input type="hidden" name="_method" value="DELETE">
                                        <input type="hidden" name="_token" value="{{csrf_token()}}">
                                        <button class="btn btn-sm btn-danger" onclick="return confirm('确定删除?')">删除</button>
                                    </form>
                                </td>
                            </tr>

                            read1

                        @endforeach
                    @else
                        <h1>没有学生名单,请管理员添加</h1>
                    @endif
                </table>
                <?php echo $users->render(); ?>
            </div>
            read2
        </div>

    </div>
@stop

注意两个 read 区域,后面用来填充其他功能,这个时候登录管理员帐号密码,你应该能看到下面的效果

Index

<?php echo $users->render(); ?> 输出分页列表,现在我们只有两组数据,所以暂时看不到,待会完成添加学生的功能之后再来测试这个效果.我们先做删除功能,可以看到删除按钮提交到
http://localhost:8000/admin/1210311232, form表单里面有个值为DELETE的隐藏输入域,告诉路由,这个请求对应这资源控制器的 destory 方法,我们来完成

public function destroy(User $user)
{
    $name = $user->name;
    $user->delete();
    session()->flash('message', $name."同学已经被移除");
    return Redirect::back();
}

看到参数 User $user, 为什么不是id,你一定会这样想.我们打开 App/Http/Provider/RouteServiceProvider.php,修改boot方法,这里我们可以查阅官方文档,路由模型绑定

public function boot(Router $router)
{
    parent::boot($router);

    $router->bind('admin', function($id){
        return \App\User::findOrFail($id);
    });

    //$router->model('admin', 'App/User');
}

上面两种写法都行,看个人喜好.上面写法比较直观,相信你现在也知道了destory方法参数为什么可以那样写了,你可以在destory中dd($user);在浏览器中点击删除,确定删除,就可以看到页面中输出对应的user信息,下面,去掉destory中dd($user),刷新,可以看到对应同学消失,并且出现提示信息,还是贴个图片,我觉得这样能反馈一些信息.

Delete

可能看到上面图片中的右侧栏吧,我们先把它完成,然后一一实现它的功能.将index视图文件中的read2替换成:

@include('Admin.right_bar')

接着新建Admin/right_bar.blade.php:

<div class="col-md-2">
    <h3>总人数: {{ $count }}</h3>
    <a href="/admin"><button class="btn btn-success btn-lg">学生列表</button></a>
    <br /><br />
    <a href="/admin/create"><button class="btn btn-primary btn-lg">添加学生</button></a>
    <br /><br />
    <a href="/admin/grade"><button class="btn btn-info btn-lg">成绩排名</button></a>
    <br /><br />
    <a href="{{ URL::route('download_stu_list_excel') }}"><button class="btn btn-default btn-lg">下载名单</button></a>
    <br /><br />
    <a href="{{ URL::route('download_grade_list_excel') }}"><button class="btn btn-lg btn-default">导出成绩</button></a>
</div>

学生列表 -- 返回学生列表,即 http://localhost:8000/admin

添加学生 -- 添加学生页面,即 http://localhost:8000/admin/create

成绩排名 -- 查看成绩列表,即 http://localhost:8000/admin/grade

下载名单 -- 下载学生信息Excel

导出成绩 -- 下载学生成绩Excel

添加学生,对应AdminController中的create方法:

public function create(){
    $result = User::where('is_admin', 0);
    $count = $result->count();
    return view('Admin.create', compact('count'));
}

接着去创建Admin/create.blade.php:

@extends('master')

@section('title')
    添加学生
@stop

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-10">
                <h2>添加学生</h2>
                <hr />

                @include('errors.list')

                <div class="form-group">
                    {!! Form::model($user = new \App\User, ['url' => 'admin/', 'class' => 'form-horizontal']) !!}
                        <div class="form-group">
                            {!! Form::label('id', '学号: ', ['class' => 'control-label col-md-1']) !!}
                            <div class="col-md-4">
                                {!! Form::text('id', old('id'), ['class' => 'form-control']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            {!! Form::label('name', '姓名: ', ['class' => 'control-label col-md-1']) !!}
                            <div class="col-md-4">
                                {!! Form::text('name', old('name'), ['class' => 'form-control', 'required']) !!}
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-5">
                                {!! Form::submit('完成,创建', ['class' => 'btn btn-success form-control']) !!}
                            </div>
                        </div>
                    {!! Form::close() !!}
                </div>
            </div>
            @include('Admin.right_bar')
        </div>
    </div>
@stop

点击添加学生:

create

这里我们Form::model(obj, [options]),传入一个新的对象$user, 这里可以查看 /vendor/illuminate/html/FormBuilder.php中的model方法,Form自动帮你填好表单,这里因为是新建,表单为空,后面你就明白了. 接着看我们的url地址, http://localhost:8000/admin,对应控制器中的store方法,

public function store(Request $request)
{
    $this->validate($request, [
        'id' => 'required|digits:10|unique:users',
        ]);
    $user = new User;
    $user->id = $request->id;
    $user->name = $request->name;
    $user->password = Hash::make($user->id);
    $user->save();
    session()->flash('message', $user->name."同学添加成功");
    DB::insert('insert into grades (user_id, math, english, c, sport, think,soft)
        values (?,?,?,?,?,?,?)', [$request->id,null,null,null,null,null,null]);
    return Redirect::to('admin');
}

我解释一下,这里,也就是我的思路,管理员只能添加学生初始化它的学号,姓名,密码(默认为学号),同时在grades表中添加对应的一条记录,至于写法为什么这么不优雅!因为我尝试其他的都不行,你有兴趣可以试试,有好的方法也可以提交给我,创建成功后返回admin并提示信息.

现在我们开始添加学生吧,超过10条就可以了,以便于我们后面的效果测试

list

接下来完成我们更新成绩功能.

#上传分数
Route::post('admin/upload_grade', [
    'as' => 'upload_grade', 'uses' => 'Admin\AdminController@upload_grade']);

这是一个post路由(响应post http请求), 现在完成更新分数的form表达,讲前面的redd1替换成

@include('Admin.upload_grade')

创建Admin/upload_grade.blade.php:

<div class="modal fade" id="myModal{{ $user->id }}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-sm">
        <div class="modal-content">

            <div class="modal-header">
              <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
              <h4 class="modal-title" id="myModalLabel">{{ $user->name }}</h4>
            </div>
            {!! Form::model($user->grade, ['url' => '/admin/upload_grade', 'class' => 'form-horizontal']) !!}

              <div class="modal-body">

                {!! Form::hidden('user_id', $user->id) !!}
                <h4>
                  {!! Form::label('math', '高数: ', ['class' => 'control-label']) !!}
                  {!! Form::text('math', null, ['class' => 'form-control', 'required']) !!}
                </h4>
                <h4>
                  {!! Form::label('english', '英语: ', ['class' => 'control-label']) !!}
                  {!! Form::text('english', null, ['class' => 'form-control', 'required']) !!}
                </h4>
                <h4>
                  {!! Form::label('c', 'C语言: ', ['class' => 'control-label']) !!}
                  {!! Form::text('c', null, ['class' => 'form-control', 'required']) !!}
                </h4>
                <h4>
                  {!! Form::label('sport', '体育: ', ['class' => 'control-label']) !!}
                  {!! Form::text('sport', null, ['class' => 'form-control', 'required']) !!}
                </h4>
                <h4>
                  {!! Form::label('think', '思修: ', ['class' => 'control-label']) !!}
                  {!! Form::text('think', null, ['class' => 'form-control', 'required']) !!}
                </h4>
                <h4>
                  {!! Form::label('soft', '软件: ', ['class' => 'control-label']) !!}
                  {!! Form::text('soft', null, ['class' => 'form-control', 'required']) !!}
                </h4>

              </div>

              <div class="modal-footer">
                {!! Form::button('关闭', ['class' => 'btn btn-default', 'data-dismiss' => 'modal']) !!}
                {!! Form::submit('提交', ['class' => 'btn btn-success']) !!}
              </div>

            {!! Form::close() !!}
        </div>
    </div>
</div>

现在刷新浏览器,点击更新分数

upload_grade

接下来就可以完成分数的上传功能,打开AdminController,添加:

public function upload_grade(Request $request)
{
    $this->validate($request, Grade::rules());
    $grade = Grade::where('user_id', $request->user_id)->first();
    $grade->math = $request->math;
    $grade->english = $request->english;
    $grade->c = $request->c;
    $grade->sport = $request->sport;
    $grade->think = $request->think;
    $grade->soft = $request->soft;
    $grade->save();
    session()->flash('message', '成绩提交成功');
    return Redirect::back();
}

这里有个地方有点问题,分数应该是0-100,参考前面Grade.php中的静态方法

digits_between:1,2   #代表是数字1位到2位,也就是0-99

本来应该使用 min:0|max:100 但是这里实际会把分数当作字符串处理,而不是数字,这样写,会判断成0-100长度的字符串,算是一个小bug吧,就先这样,我还没见过我们学校有考试100的人,哈哈!

下面,我们给每个人上传一下分数.你现在能随便登录一个学生帐号密码,能查询到自己的成绩

对了,别忘了在validation.php里面设置你的提示信息,这里就不再赘述.

接着,我们来完成成绩排名,看我们的路由,先新建Grade控制器:

php artisan make:controller Admin/GradeController --plain

两个方法:

public function __construct()
{
    $this->middleware('admin');
}

public function index()
{
    $result = User::where('is_admin', 0);
    $users = $result->get();
    $count = $result->count();
    return view('Admin.list', compact('count', 'users'));
}

$users是我们的学生信息资源,传递到Admin/list.blade.php视图.

@extends('master')

@section('title')
    学生成绩列表
@stop

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-10">
                <h3 align="center">学生成绩表</h3>
                <table class="table table-striped" id="sortTable">
                    <thead>
                        <tr>
                            <th class="col-md-2">学号 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>姓名 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>高数 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>英语 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>C语言 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>体育 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>思修 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                            <th>软件 <a href="javascript:void(0)"><span class="glyphicon glyphicon-sort" aria-hidden="true"></span></a></th>
                        </tr>
                    </thead>

                    @foreach ($users as $user)
                        <tr>
                            <td>{{$user->id}}</td>
                            <td>{{$user->name}}</td>
                            <td>{{$user->grade->math}}</td>
                            <td>{{$user->grade->english}}</td>
                            <td>{{$user->grade->c}}</td>
                            <td>{{$user->grade->sport}}</td>
                            <td>{{$user->grade->think}}</td>
                            <td>{{$user->grade->soft}}</td>
                        </tr>
                    @endforeach
                </table>
            </div>

            @include('Admin.right_bar')

        </div>
    </div>
@stop

在浏览其中点击成绩排名,你能看到所有人的成绩表吧

grade1

那么,问题来了,首先,分数太多,不能突出重点(我认为是不及格的分数),第二不能将科目分数进行排序,我们来一个一个解决.

分数的问题,我觉得讲不及格分数显示为红色比较好,js有点基础的同学应该很中意就办到了.

我们先给tr设置一个class属性

...
@foreach ($users as $user)
    <tr class="myGrade">
...

打开我们的main.js,添加:

$('.myGrade').each(function(){
    $(this).children().each(function(){
        if ($(this).html() < 60){
            $(this).addClass("notPass");
        }
    });
});

然后新建一个mian.css文件,放在public/css目录下

.notPass{
    color: red;
}

这个时候你再次刷新浏览器,你可以看到这样的效果了(有点夸张,大学挂科人数不多的)

grade2

接着完成表单的排序,官网打开速度很慢,可自行百度下载,或者去项目源文件找到 /resource/js/jquery.tablesorter.min.js ,拷贝到public/js/目录下, 在master.blade.php下引入

<script type="text/javascript" src="/js/jquery.tablesorter.min.js"></script>

然后在main.js里面添加这样一行:

$("#sortTable").tablesorter();

回到list视图文件,给table添加一个id 为 sortTable

刷新浏览器,你就能点击表头进行排序了.更多用法你也可以去参考官方文档

现在我们可以使用前端自动化工具来处理我们的css和js脚本文件了

安装node,安装gulp,在根目录下执行:

npm install

如果你实在安装不成功,可以去googel各种安装方法,或者跳过此节,这样只讲述了基础用法,
我们接着继续resources目录下创建css和js文件夹,接着将以下文件复制到:

js/
  bootstrap.min.js
  jquery.min.js
  jquery.tablesorter.min.js
  main.js

css/
  bootstrap.min.js
  main.js

然后打开gulpfile.js,修改:

var elixir = require('laravel-elixir');

elixir(function(mix) {
    mix.styles([
        "bootstrap.min.css",
        "main.css"
        ]);

    mix.scripts([
        "jquery.min.js",
        "bootstrap.min.js",
        "jquery.tablesorter.min.js",
        "main.js"
        ]);
});

接着执行:

gulp

你就可以找到 public/css/all.css 和 public/js/all.js文件了

接着你可以删除master.blade.php里所有引入的css和js脚本,现在只需要引入两个文件:

<link rel="stylesheet" type="text/css" href="{{ asset('/css/all.css') }}">

<script type="text/javascript" src="/js/all.js"></script>

刷新浏览器,查看各个功能,正常使用(确保public文件夹下引入bootstrap的fonts文件)

最后,我们完成名单表和成绩表的导出功能,这里我们使用laravel Excel

在composer.json require中加入:

"maatwebsite/excel": "~2.0.0"

执行:

composer update

安装成功后在config/app.php中加入

'Maatwebsite\Excel\ExcelServiceProvider',

'Excel'     => 'Maatwebsite\Excel\Facades\Excel',

然后我们先测试一下是否能成功运行,参考官方文档,Laravel Excel,测试各种功能

这里我只是演示一下,新建一个TestController,在route.php中添加

Route::get('/test', 'TestController@index');

在TestController的index方法中添加:

Excel::create('测试', function($excel) {

$excel->sheet('Sheetname', function($sheet) {

    $sheet->fromArray(array(
        array('data1', 'data2'),
        array('data3', 'data4')
    ));

});

})->export('xls');

然后你可以修改right_bar.blade.php中一个超链接地址为 href="/test",接着点击,你会发现浏览器提示下载文件 测试.xls,就是这么简单.

后面的内容可以在github上查看,(哈哈,貌页面布局被我毁了,我不敢发在这里了,:rabbit: )

本帖已被设为精华帖!
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 4

很不错啊。赞一个

7年前 评论

牛逼 哈哈哈哈哈哈

6年前 评论

码字牛掰

4年前 评论

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