构建博客后台管理系统


一个完整的博客应用不能没有后台管理系统。所以在本节中我们将继续完善博客应用 —— 开发后台管理系统。

1、创建路由

在上一节十分钟创建博客项目中,已经设置过了 routes/web.php,现在,我们要添加后台路由到该文件。

为什么要使用路由?

Laravel 提供一种机制用来建立 Web 请求与处理 Web 请求的代码之间的关系,这种机制被称作路由。本项目中所有 Web 路由都定义在 routes/web.php 文件中。

只要 Web 请求路径在 public 目录下找不到(非静态资源请求),Laravel 就会从路由文件查找对应关系并返回响应。

构建后台系统之前,先在 routes/web.php 中注册后台路由:

// 后台路由
Route::get('/admin', function () {
    return redirect('/admin/post');
});
Route::middleware('auth')->namespace('Admin')->group(function () {
    Route::resource('admin/post', 'PostController');
    Route::resource('admin/tag', 'TagController');
    Route::get('admin/upload', 'UploadController@index');
});

// 登录退出
Route::get('/login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController@login');
Route::get('/logout', 'Auth\LoginController@logout')->name('logout');

保存好 routes/web.php 后,下一步我们要创建对应的控制器。

2、创建后台控制器

使用 Artisan 命令生成控制器:

php artisan make:controller Admin/PostController --resource
php artisan make:controller Admin/TagController --resource
php artisan make:controller Admin/UploadController

上述三个命令运行完成后,会在 app/Http/Controllers/Admin 目录下生成三个控制器。

修改 PostController 类的 index() 方法如下:

 /**
  * Display a listing of the posts.
  *
  * @return Response
  */
public function index()
{
    return view('admin.post.index');
}

现在 index() 方法只是简单渲染视图,稍后我们会完善它。

3、创建视图

我们还需要创建一些视图,一步步来好了。

移除 Vue 代码

由于我们在本项目中不会用到 Vue 组件,所以需要将对应初始化代码移除以免影响后续开发,在 resources/js/app.js 中移除以下代码:

window.Vue = require('vue');

/**
 * The following block of code may be used to automatically register your
 * Vue components. It will recursively scan this directory for the Vue
 * components and automatically register them with their "basename".
 *
 * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
 */

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

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key)))

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

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

然后运行 npm run dev 重新编译前端资源,让修改生效。

创建后台布局

Blade 模板引擎是 Laravel 提供的最强大的功能之一,接下来我们将基于 Blade 创建一个供后台使用的布局文件,从而让后台有一个整体外观一致的视觉体验。

resources/views 目录下新建一个 admin 目录,然后在该目录下创建一个 layout.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">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('blog.title') }} 管理后台</title>

    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    @yield('styles')

</head>
<body>
{{-- Navigation Bar --}}
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
    <div class="container">
        <a class="navbar-brand mr-auto mr-lg-0" href="#">{{ config('blog.title') }} 后台</a>
        <button class="navbar-toggler p-0 border-0" type="button" data-toggle="collapse" data-target="#navbar-menu"
                aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbar-menu">
            @include('admin.partials.navbar')
        </div>
    </div>
</nav>

<main class="py-4">
    @yield('content')
</main>
<script src="{{ asset('js/app.js') }}"></script>

@yield('scripts')
</body>
</html>

上面的代码片段可能看上去很熟悉,没错,这就是 Bootstrap 的基本模板,我们只是在其基础上加了一些额外的钩子:

  • <title>{{ config('blog.title') }} Admin</title> : 设置站点标题
  • @yield('styles'):该 Blade 指令将会输出继承自该布局的子视图的 styles 区块内容(如果有的话),其目的在于将 CSS 样式文件放到模板顶部。
  • @include('admin.partials.navbar'):这里我们引入另一个 Blade 模板(现在还不存在)
  • @yield('content'):输出页面的主体内容
  • @yield('scripts'):输出额外的 JavaScript 脚本文件

创建导航条局部视图

resources/views/admin 目录下新建 partials 目录,并在该目录下创建 navbar.blade.php 文件,编辑其内容如下:

<ul class="navbar-nav mr-auto">
    <li class="nav-item"><a class="nav-link" href="/">首页</a></li>
    @auth
        <li @if (Request::is('admin/post*')) class="nav-item active" @else class="nav-item" @endif>
            <a class="nav-link" href="/admin/post">文章</a>
        </li>
        <li @if (Request::is('admin/tag*')) class="nav-item active" @else class="nav-item" @endif>
            <a class="nav-link" href="/admin/tag">标签</a>
        </li>
        <li @if (Request::is('admin/upload*')) class="nav-item active" @else class="nav-item" @endif>
            <a class="nav-link" href="/admin/upload">上传</a>
        </li>
    @endauth
</ul>

<ul class="navbar-nav ml-auto">
    @guest
        <li class="nav-item"><a class="nav-link" href="/login">登录</a></li>
    @else
        <li class="nav-item dropdown">
            <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button"
               aria-expanded="false">
                {{ Auth::user()->name }}
                <span class="caret"></span>
            </a>
            <div class="dropdown-menu" role="menu">
                <a class="dropdown-item" href="/logout">退出</a>
            </div>
        </li>
    @endguest
</ul>

如果用户登录进来,该模板会显示一个顶部导航条:左侧包含「文章」、「标签」和「上传」链接,右侧包含「退出」链接。

如果用户没有登录,只在导航条右侧显示「登录」链接。

创建登录表单

现在我们已经有了后台布局视图,创建登录表单不要太简单,首先在 resources/views/admin 目录下新建 auth 目录,并在该目录下创建 login.blade.php 文件,编辑该文件内容如下:

@extends('admin.layout')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">用户登录</div>
                    <div class="card-body">

                        @include('admin.partials.errors')

                        <form role="form" method="POST" action="{{ url('/login') }}">
                            <input type="hidden" name="_token" value="{{ csrf_token() }}">

                            <div class="form-group row">
                                <label class="col-md-4 col-form-label text-md-right">邮箱</label>
                                <div class="col-md-6">
                                    <input type="email" class="form-control" name="email" value="{{ old('email') }}"
                                           autofocus>
                                </div>
                            </div>

                            <div class="form-group row">
                                <label class="col-md-4 col-form-label text-md-right">密码</label>
                                <div class="col-md-6">
                                    <input type="password" class="form-control" name="password">
                                </div>
                            </div>

                            <div class="form-group row">
                                <div class="col-md-6 offset-md-4">
                                    <div class="form-check">
                                        <label>
                                            <input class="form-check-input" type="checkbox" name="remember"> 记住我
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <div class="form-group row mb-0">
                                <div class="col-md-8 offset-md-4">
                                    <button type="submit" class="btn btn-primary">登录</button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

在登录表单中我们引入了一个尚未创建的 admin.partials.errors,接下来就来创建该视图。

创建错误局部视图

验证表单输入错误并在视图中显示这些错误在处理表单时是一个通用任务,所以我们将其放到一个单独的 Blade 模板视图中进行处理。

resources/views/admin/partials 目录下创建 errors.blade.php,编辑其内容如下:

@if ($errors->any())
    <div class="alert alert-danger">
        <strong>Whoops!</strong>
        There were some problems with your input.<br><br>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

$errors 变量在每个视图中都有效,其中包含了一个错误集合(如果有错误的话),我们只需要检查是否包含错误并将错误显示出来即可。

创建文章列表视图

resources/views/admin 目录下创建一个新的目录 post,并在该目录下新建 index.blade.php,编辑该文件内容如下:

@extends('admin.layout')

@section('content')
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="card">
                    <div class="card-header">
                        <h3 class="card-title">文章列表</h3>
                    </div>
                    <div class="card-body">

                        TODO

                    </div>
                </div>
            </div>
        </div>
    </div>
@stop

这只是一个临时视图,后面我们会逐步完善它。

4、测试登录/退出

在测试登录功能之前,需要先在 app/Http/Controllers/Auth/LoginController.php 控制器中重写 showLoginForm 方法,将默认登录页面修改为我们自定义的页面视图:

public function showLoginForm()
{
    return view('admin.auth.login');
}

并且修改登录后跳转链接到博客后台:

protected $redirectTo = '/admin';

还有一个地方需要同步修改下,那就是检查用户是否已经登录的中间件,该中间件位于app/Http/Middleware/RedirectIfAuthenticated.php,如果用户已经登录则跳转到后台首页:

if (Auth::guard($guard)->check()) {
    return redirect('/admin');
}

然后在浏览器中访问 http://blog57.test/admin,会跳转到登录页面,页面显示如下:

你可以试着登录,当然你不可能登录成功,因为我们还没有创建后台用户。

创建后台用户

php artisan tinker 可以用于通过命令行与应用进行交互,下面我们在 Laradock 中项目根目录下使用该命令为博客项目创建后台用户:

现在我们就可以使用刚刚创建的用户登录后台了。回到登录页试试吧。

登录成功后页面跳转到后台文章列表页:

修改退出后跳转 URL

如果点击页面顶部导航条右侧的下拉列表并选择 Logout 退出,你会发现页面没有跳转到登录页面,而是博客页面。

为什么会这样?

如果查看 LoginController 类(app/Http/Controllers/Auth/LoginController)的 logout() 方法,你会发现并没有该方法,在其父类中也没有 logout() 方法,这是因为 LoginController 使用了 AuthenticatesUsers Trait,logout() 方法正是定义在了 AuthenticatesUsers Trait 中。

如果你在 vendor/laravel/framework/src 目录下找到 Illuminate/Foundation/Auth 目录,你会发现该目录下有一个 AuthenticatesUsers.php,在该文件中,可以看到 logout() 方法:

public function logout(Request $request)
{
    $this->guard()->logout();

    $request->session()->invalidate();

    return $this->loggedOut($request) ?: redirect('/');
}

并且该方法在退出完成后先执行 loggedOut 方法,如果该方法未定义则重定向到了根路径 /

要修改重定向路径,可以在 app/Http/Controllers/Auth/LoginController 中重写 Trait 中的 loggedOut 方法:

// 退出后重定向到登录页
public function loggedOut(Request $request)
{
    return redirect('/login');
}

该类的其余部分和之前一样保持不变。我们不允许普通用户注册,所以没有用户注册提供相应的路由。

登录/退出

由于现在我们已经将 logout() 方法的跳转链接做了修改,现在我们已经可以成功实现登录/退出了。试试吧。

5、引入 DataTables 和 FontAwesome

此外,我们还需要在项目中引入前端表格库 DataTables,以便让我们的 HTML 表格具备更强大的交互功能和更美观的渲染效果,因为我们的前端是基于 Bootstrap 4 开发的,所以需要安装一个适配 Bootstrap 4 的 DataTables,通过 NPM 进行安装:

npm install datatables.net-bs4 --save-dev

安装完成后,在 resources/js/bootstrap.js 中引入它的 JS 库:

try {
    window.Popper = require('popper.js').default;
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
    require('datatables.net-bs4');

    ...

然后在 resources/sass/app.scss 引入其 CSS 库:

@import "~datatables.net-bs4/css/dataTables.bootstrap4";

接下来,还要引入图标字体库 FontAwesome,还是通过 NPM 安装:

npm install @fortawesome/fontawesome-free --save-dev

安装完成后在 resources/sass/app.scss 中引入即可:

// Font Awesome
@import '~@fortawesome/fontawesome-free/scss/brands';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import "~@fortawesome/fontawesome-free/scss/fontawesome";

最后重新编译下前端资源:

npm run dev

接下来,就可以在项目中使用 DataTables 和 FontAwesome 了。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 十分钟完成博客系统搭建

>> 下一篇: 在后台实现文章标签增删改查功能