构建博客后台管理系统
一个完整的博客应用不能没有后台管理系统。所以在本节中我们将继续完善博客应用 —— 开发后台管理系统。
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 了。
64 Comments
院长 ,运行 npm run dev 报如下错误:
The system cannot find the path specified. events.js:174 throw er; // Unhandled 'error' event ^
Error: spawn node_modules\webpack\bin\webpack.js ENOENT at notFoundError (D:\www\runingBlog\node_modules_cross-spawn@6.0.5@cross-spawn\lib\enoent.js:6:26) at verifyENOENT (D:\www\runingBlog\node_modules_cross-spawn@6.0.5@cross-spawn\lib\enoent.js:40:16) at ChildProcess.cp.emit (D:\www\runingBlog\node_modules_cross-spawn@6.0.5@cross-spawn\lib\enoent.js:27:25) at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12) Emitted 'error' event at: at ChildProcess.cp.emit (D:\www\runingBlog\node_modules_cross-spawn@6.0.5@cross-spawn\lib\enoent.js:30:37) at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12) npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! @ development:
cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the @ development script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\Administrator\AppData\Roaming\npm-cache_logs\2019-06-24T03_59_01_875Z-debug.log npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! @ dev:
npm run development
npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the @ dev script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\Administrator\AppData\Roaming\npm-cache_logs\2019-06-24T03_59_01_904Z-debug.log
我创建后台用户是,$user->save();运行报错php_network_getaddresses: getaddrinfo failed:,百度后没有有效解决方法(DB_HOST=mysql),但是其他之前写的文章页面浏览正常,数据可以查询并显示出来。
看起来是数据库连接的问题 是使用 laradock 作为开发环境的吗
对的,DB_HOST无论怎么设置,命令行模式下都无法进行save,但是如果是文章列表页面,数据只有在DB_HOST=mysql时才能获取
错误信息:Illuminate/Database/QueryException with message 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: ֪ (SQL: insert in to
users
(name
,email
,password
,updated_at
,created_at
) values (wuhao, 1216252072@qq.com, $2y$10$NDt37sM5BiYRcnwkXasIIOPnddN8i0b57F9lS7CllY08LRF42c3rO, 2019-07-22 06:28:37, 2019-07-22 06:28:37))' 环境:docker-desktop laradock (mysql version = 5.7)你命令行是在宿主机执行还是在 workspace 里面执行的 得到 workspace 里面执行才行
感谢感谢,我一直用了宿主机的命令行,测试了一下,没报错了。
怪不得我用户创建不了;原来少了对象初始化
你好,按教程走的,bootstrap样式没有引入进来,提示
http://blog.test/css/app.css net::ERR_ABORTED 404 (Not Found)
nginx 根目录设置的是 /var/www/blog57/public,请问是什么原因呢不好意思,找到原因了~