教程源於:Laravel學院php
繼文件上傳後呢,咱來搞一搞文章的事情。css
咱們須要改改數據表的結構 由於涉及到重命名列名 因此咱須要引入一個包:Doctrine:html
composer require "doctrine/dbal"
php artisan make:migration restructure_posts_table --table=posts
class RestructurePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('posts', function (Blueprint $table) { // 在title字段後添加subtitle 文章的副標題 $table->string('subtitle')->after('title'); // 把content改成content_raw Markdown格式的文本 $table->renameColumn('content', 'content_raw'); // 在content字段後添加content_html 使用 Markdown 編輯內容但同時保存 HTML 版本 $table->text('content_html')->after('content'); // 在content_html字段後添加page_image 文章使用到縮略圖 $table->string('page_image')->after('content_html'); // 在page_image字段後添加meta_description 文章說明 $table->string('meta_description')->after('page_image'); // 在meta_description字段後添加is_draft 是不是草稿 $table->boolean('is_draft')->after('meta_description'); // 在is_draft字段後添加layout 並設置默認值 使用的佈局 $table->string('layout')->after('is_draft')->default('blog.layouts.post'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('posts', function (Blueprint $table) { $table->dropColumn('subtitle'); $table->dropColumn('content_html'); $table->dropColumn('page_image'); $table->dropColumn('meta_description'); $table->dropColumn('is_draft'); $table->dropColumn('layout'); $table->renameColumn('content_raw', 'content'); }); } }
運行命令後看眼數據庫是否已經修改爲功:前端
php artisan migrate
文章和標籤是多對多的關係,因此先建立遷移文件,而後記得運行遷移:jquery
class CreatePostTagPivot extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('post_tag_pivot', function (Blueprint $table) { $table->increments('id'); $table->integer('tag_id')->unsigned()->index(); $table->integer('post_id')->unsigned()->index(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('post_tag_pivot'); } }
class Tag extends Model { protected $fillable = ['tag', 'title', 'subtitle', 'page_image', 'meta_description', 'layout', 'reverse_direction']; // 定義關係 public function posts() { return $this->belongsToMany(Post::class, 'post_tag_pivot'); } /** * 批量建立須要的tag * * @param array $tags */ public static function addNeededTags(array $tags) { if (count($tags) === 0){ return; } // 經過tag字段在$tags數組中查找,把找到的模型經過lists來獲取全部的tag字段 $found = static::WhereIn('tag', $tags)->lists('tag')->all(); foreach (array_diff($tags, $found) as $tag) { // 把不存在的tag進行建立 其餘字段先自動填充 static::create([ 'tag' => $tag, 'title' => $tag, 'subtitle' => 'Subtitle for '.$tag, 'page_image' => '', 'meta_description' => '', 'reverse_direction' => false, ]); } } }
咱們一步步的來,首先先來展現咱們的文章吧。laravel
public function index() { return view('admin.post.index')->withPosts(Post::all()); }
@extends('admin.layout') @section('content') <div class="container-fluid"> <div class="row page-title-row"> <div class="col-md-6"> <h3>Post <small> >> Listing</small></h3> </div> <div class="col-md-6 text-right"> <a href="/admin/post/create" class="btn btn-success btn-md"> <i class="fa fa-plus-circle"></i> New Post </a> </div> </div> <div class="row"> <div class="col-sm-12"> @include('admin.partials.error') @include('admin.partials.success') <table id="posts-table" class="table table-bordered table-striped"> <thead> <tr> <td>Published</td> <td>Title</td> <td>Subtitle</td> <td data-sortable="false">Published</td> </tr> </thead> <tbody> @foreach($posts as $post) <tr> <td data-order="{{ $post->published_at->timestamp }}"> {{ $post->published_at->format('j-M-y g:ia') }} </td> <td>{{ $post->title }}</td> <td>{{ $post->subtitle }}</td> <td> <a href="/admin/post/{{ $post->id }}/edit" class="btn btn-xs btn-info"> <i class="fa fa-edit"></i> Edit </a> <a href="/blog/{{ $post->slug }}" class="btn btn-xs btn-warning"> <i class="fa fa-eye"></i> View </a> </td> </tr> @endforeach </tbody> </table> </div> </div> </div> @endsection @section('scripts') <script> $(function () { $("#posts-table").DataTable({ order: [[0, "desc"]] }); }); </script> @endsection
public function create() { $data = $this->dispatch(new PostFormFields()); return view('admin.post.create', $data); }
在上面的代碼中咱們使用到了一個任務,這個任務就是返回每一個字段的默認值web
建立create方法中用到的job:數據庫
php artisan make:job PostFormFields
在app/Jobs中找到剛剛建立的job編輯以下:json
class PostFormFields extends Job implements SelfHandling { protected $id; protected $fieldList = [ 'title' => '', 'subtitle' => '', 'page_image' => '', 'content' => '', 'meta_description' => '', 'is_draft' => "0", 'publish_date' => '', 'publish_time' => '', 'layout' => 'blog.layouts.post', 'tags' => [], ]; /** * Create a new job instance. * * @return void */ public function __construct($id = null) { $this->id = $id; } /** * Execute the job. * * @return void */ public function handle() { $fields = $this->fieldList; if ($this->id){ $fields = $this->fieldsFromModel($this->id, $fields); } else { $when = Carbon::now()->addHour(); $fields['publish_date'] = $when->format('M-j-Y'); $fields['publish_time'] = $when->format('g:i A'); } foreach ($fields as $fieldName => $fieldValue) { $fields[$fieldName] = old($fieldName, $fieldValue); } return array_merge($fields, ['allTags' => Tag::lists('tag')->all()]); } /** * 取出模型中的數據 * * @param $id * @param array $fields * @return array */ protected function fieldsFromModel($id, array $fields) { $post = Post::findOrFail($id); $fieldNames = array_keys(array_except($fields, ['tags'])); $fields = ['id' => id]; foreach ($fieldNames as $field) { $fields[$field] = $post->$field; } $fields['tags'] = $post->tags()->lists('tag')->all(); return $fields; } }
在job中咱們使用了publishe_date和time,咱們來實現這些get:gulp
/** * 設置ContentRaw的同時設置ContentHTML。 * * @param $value */ public function setContentRawAttribute($value) { $this->attributes['content_raw'] = $value; $this->attributes['content_html'] = Markdown::convertToHtml($value); } /** * 使用content快捷的返回content_raw * * @param $value * @return mixed */ public function getContentAttribute($value) { return $this->content_raw; } /** * 快捷返回publish_time * * @param $value * @return mixed */ public function getPublishTimeAttribute($value) { return $this->published_at->format('g:i A'); } /** * 快捷返回publish_date * * @param $value * @return mixed */ public function getPublishDateAttribute($value) { return $this->published_at->format('M-j-Y'); }
咱們在create視圖中須要用到兩個前端資源:Selectize.js(下拉列表功能)和Pickadate.js(日期插件),咱們來使用Bower下載:
bower install selectize --save
bower install pickadate --save
使用Gulp來整理前端資源:
var gulp = require('gulp'); var rename = require('gulp-rename'); var elixir = require('laravel-elixir'); /* |-------------------------------------------------------------------------- | Elixir Asset Management |-------------------------------------------------------------------------- | | Elixir provides a clean, fluent API for defining some basic Gulp tasks | for your Laravel application. By default, we are compiling the Less | file for our application, as well as publishing vendor resources. | */ /** * 拷貝操做 */ gulp.task("copyfiles", function(){ // js gulp.src("vendor/bower_dl/jquery/dist/jquery.js") .pipe(gulp.dest("resources/assets/js/")); // bootstrap gulp.src("vendor/bower_dl/bootstrap/less/**") .pipe(gulp.dest("resources/assets/less/bootstrap")); gulp.src("vendor/bower_dl/bootstrap/dist/js/bootstrap.js") .pipe(gulp.dest("resources/assets/js/")); // font 不用編譯和合並 直接複製到public就能夠 gulp.src("vendor/bower_dl/bootstrap/fonts/**") .pipe(gulp.dest("public/assets/fonts")); // awesome gulp.src("vendor/bower_dl/font-awesome/less/**") .pipe(gulp.dest("resources/assets/less/fontawesome")); gulp.src("vendor/bower_dl/font-awesome/fonts/**") .pipe(gulp.dest("public/assets/fonts")); // 拷貝 datatables var dtDir = 'vendor/bower_dl/datatables.net-plugins/integration/'; gulp.src("vendor/bower_dl/datatables/media/js/jquery.dataTables.js") .pipe(gulp.dest('resources/assets/js/')); gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.css') .pipe(rename('dataTables.bootstrap.less')) .pipe(gulp.dest('resources/assets/less/others/')); gulp.src(dtDir + 'bootstrap/3/dataTables.bootstrap.js') .pipe(gulp.dest('resources/assets/js/')); // 拷貝selectize gulp.src("vendor/bower_dl/selectize/dist/css/**") .pipe(gulp.dest("public/assets/selectize/css")); gulp.src("vendor/bower_dl/selectize/dist/js/standalone/selectize.min.js") .pipe(gulp.dest("public/assets/selectize/")); // 拷貝 pickadate gulp.src("vendor/bower_dl/pickadate/lib/compressed/themes/**") .pipe(gulp.dest("public/assets/pickadate/themes/")); gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.js") .pipe(gulp.dest("public/assets/pickadate/")); gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.date.js") .pipe(gulp.dest("public/assets/pickadate/")); gulp.src("vendor/bower_dl/pickadate/lib/compressed/picker.time.js") .pipe(gulp.dest("public/assets/pickadate/")); }); elixir(function(mix) { // 合併腳本文件 mix.scripts([ 'js/jquery.js', 'js/bootstrap.js', 'js/jquery.dataTables.js', 'js/dataTables.bootstrap.js' ], 'public/assets/js/admin.js', 'resources/assets' ); // 編譯 Less mix.less('admin.less', 'public/assets/css/admin.css'); });
運行Gulp:
gulp copyfiles
gulp
@extends("admin.layout") {{-- 樣式表 --}} @section('styles') <link rel="stylesheet" href="/assets/selectize/css/selectize.css"> <link rel="stylesheet" href="/assets/selectize/css/selectize.bootstrap3.css"> <link rel="stylesheet" href="/assets/pickadate/themes/default.css"> <link rel="stylesheet" href="/assets/pickadate/themes/default.date.css"> <link rel="stylesheet" href="/assets/pickadate/themes/default.time.css"> @endsection {{--content--}} @section("content") <div class="container-fluid"> <div class="row page-title-row"> <div class="col-md-12"> <h3>Posts <small> >> Add New Post</small></h3> </div> </div> <div class="row"> <div class="col-sm-12"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">New Post Form</h3> </div> <div class="panel-body"> @include('admin.partials.error') <form action="{{ route("admin.post.store") }}" method="POST" class="form-horizontal"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> @include('admin.post._form') <div class="col-md-8"> <div class="form-group"> <div class="col-md-10 col-md-offset-2"> <button class="btn btn-primary btn-lg" type="submit"> <i class="fa fa-disk-o"></i> Save New Post </button> </div> </div> </div> </form> </div> </div> </div> </div> </div> @endsection {{--scripts--}} @section('scripts') <script src="/assets/pickadate/picker.js"></script> <script src="/assets/pickadate/picker.date.js"></script> <script src="/assets/pickadate/picker.time.js"></script> <script src="/assets/selectize/selectize.min.js"></script> <script> $(function() { $("#publish_date").pickadate({ format: "mmm-d-yyyy" }); $("#publish_time").pickatime({ format: "h:i A" }); $("#tags").selectize({ create: true }); }); </script> @endsection
建立咱們在上面用到的_form.blade.php:
<div class="row"> <div class="col-md-8"> <div class="form-group"> <label for="title" class="col-md-2 control-label"> Title </label> <div class="col-md-10"> <input type="text" class="form-control" name="title" autofocus id="title" value="{{ $title }}"> </div> </div> <div class="form-group"> <label for="subtitle" class="col-md-2 control-label"> Subtitle </label> <div class="col-md-10"> <input type="text" class="form-control" name="subtitle" id="subtitle" value="{{ $subtitle }}"> </div> </div> <div class="form-group"> <label for="page_image" class="col-md-2 control-label"> Page Image </label> <div class="col-md-10"> <div class="row"> <div class="col-md-8"> <input type="text" class="form-control" name="page_image" id="page_image" onchange="handle_image_change()" alt="Image thumbnail" value="{{ $page_image }}"> </div> <script> function handle_image_change() { $("#page-image-preview").attr("src", function () { var value = $("#page_image").val(); if ( ! value) { value = {!! json_encode(config('blog.page_image')) !!}; if (value == null) { value = ''; } } if (value.substr(0, 4) != 'http' && value.substr(0, 1) != '/') { value = {!! json_encode(config('blog.uploads.webpath')) !!} + '/' + value; } return value; }); } </script> <div class="visible-sm space-10"></div> <div class="col-md-4 text-right"> <img src="{{ page_image($page_image) }}" class="img img_responsive" id="page-image-preview" style="max-height:40px"> </div> </div> </div> </div> <div class="form-group"> <label for="content" class="col-md-2 control-label"> Content </label> <div class="col-md-10"> <textarea class="form-control" name="content" rows="14" id="content">{{ $content }}</textarea> </div> </div> </div> <div class="col-md-4"> <div class="form-group"> <label for="publish_date" class="col-md-3 control-label"> Pub Date </label> <div class="col-md-8"> <input class="form-control" name="publish_date" id="publish_date" type="text" value="{{ $publish_date }}"> </div> </div> <div class="form-group"> <label for="publish_time" class="col-md-3 control-label"> Pub Time </label> <div class="col-md-8"> <input class="form-control" name="publish_time" id="publish_time" type="text" value="{{ $publish_time }}"> </div> </div> <div class="form-group"> <div class="col-md-8 col-md-offset-3"> <div class="checkbox"> <label> <input {{ checked($is_draft) }} type="checkbox" name="is_draft"> Draft? </label> </div> </div> </div> <div class="form-group"> <label for="tags" class="col-md-3 control-label"> Tags </label> <div class="col-md-8"> <select name="tags[]" id="tags" class="form-control" multiple> @foreach ($allTags as $tag) <option @if (in_array($tag, $tags)) selected @endif value="{{ $tag }}"> {{ $tag }} </option> @endforeach </select> </div> </div> <div class="form-group"> <label for="layout" class="col-md-3 control-label"> Layout </label> <div class="col-md-8"> <input type="text" class="form-control" name="layout" id="layout" value="{{ $layout }}"> </div> </div> <div class="form-group"> <label for="meta_description" class="col-md-3 control-label"> Meta </label> <div class="col-md-8"> <textarea class="form-control" name="meta_description" id="meta_description" rows="6">{{ $meta_description }}</textarea> </div> </div> </div> </div>
上面使用到了一個幫助函數,咱們在helper.php中添加這個方法:
/** * 若是傳進來的參數是true 則返回checked,false放回空字符串。 * * @param $value * @return string */ function checked($value) { return $value ? 'checked' : ''; } /** * Return img url for headers */ function page_image($value = null) { if (empty($value)) { $value = config('blog.page_image'); } if (! starts_with($value, 'http') && $value[0] !== '/') { $value = config('blog.uploads.webpath') . '/' . $value; } return $value; }
class PostCreateRequest extends Request { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'title' => 'required', 'subtitle' => 'required', 'content' => 'required', 'publish_date' => 'required', 'publish_time' => 'required', 'layout' => 'required', ]; } /** * 返回建立模型所須要用的數據。 * * @return array */ public function postFillData() { $published_at = new Carbon( $this->publish_date.' '.$this->publish_time ); return [ 'title' => $this->title, 'subtitle' => $this->subtitle, 'page_image' => $this->page_image, 'content_raw' => $this->get('content'), 'meta_description' => $this->meta_description, 'is_draft' => (bool)$this->is_draft, 'published_at' => $published_at, 'layout' => $this->layout, ]; } }
public function store(Requests\PostCreateRequest $request) { $post = Post::create($request->postFillData()); $post->syncTags($request->get('tags', [])); return redirect() ->route('admin.post.index') ->withSuccess('New Post Successfully Created.'); }
在store方法中使用了Post模型的syncTags方法 用於同步標籤,在Post模型中建立這個方法:
public function syncTags(array $tags) { Tag::addNeededTags($tags); if (count($tags)){ $this->tags()->sync(Tag::whereIn('tag', $tags)->lists('id')->all()); return; } $this->tags()->detach(); }
如今咱們能夠實現PostController上的store方法了:
public function store(Requests\PostCreateRequest $request) { $post = Post::create($request->postFillData()); $post->syncTags($request->get('tags', [])); return redirect() ->route('admin.post.index') ->withSuccess('New Post Successfully Created.'); }
@extends('admin.layout') @section('styles') <link href="/assets/pickadate/themes/default.css" rel="stylesheet"> <link href="/assets/pickadate/themes/default.date.css" rel="stylesheet"> <link href="/assets/pickadate/themes/default.time.css" rel="stylesheet"> <link href="/assets/selectize/css/selectize.css" rel="stylesheet"> <link href="/assets/selectize/css/selectize.bootstrap3.css" rel="stylesheet"> @stop @section('content') <div class="container-fluid"> <div class="row page-title-row"> <div class="col-md-12"> <h3>Posts <small>» Edit Post</small></h3> </div> </div> <div class="row"> <div class="col-sm-12"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Post Edit Form</h3> </div> <div class="panel-body"> @include('admin.partials.error') @include('admin.partials.success') <form class="form-horizontal" role="form" method="POST" action="{{ route('admin.post.update', $id) }}"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <input type="hidden" name="_method" value="PUT"> @include('admin.post._form') <div class="col-md-8"> <div class="form-group"> <div class="col-md-10 col-md-offset-2"> <button type="submit" class="btn btn-primary btn-lg" name="action" value="continue"> <i class="fa fa-floppy-o"></i> Save - Continue </button> <button type="submit" class="btn btn-success btn-lg" name="action" value="finished"> <i class="fa fa-floppy-o"></i> Save - Finished </button> <button type="button" class="btn btn-danger btn-lg" data-toggle="modal" data-target="#modal-delete"> <i class="fa fa-times-circle"></i> Delete </button> </div> </div> </div> </form> </div> </div> </div> </div> {{-- 確認刪除 --}} <div class="modal fade" id="modal-delete" tabIndex="-1"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"> × </button> <h4 class="modal-title">Please Confirm</h4> </div> <div class="modal-body"> <p class="lead"> <i class="fa fa-question-circle fa-lg"></i> Are you sure you want to delete this post? </p> </div> <div class="modal-footer"> <form method="POST" action="{{ route('admin.post.destroy', $id) }}"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <input type="hidden" name="_method" value="DELETE"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="submit" class="btn btn-danger"> <i class="fa fa-times-circle"></i> Yes </button> </form> </div> </div> </div> </div> </div> @stop @section('scripts') <script src="/assets/pickadate/picker.js"></script> <script src="/assets/pickadate/picker.date.js"></script> <script src="/assets/pickadate/picker.time.js"></script> <script src="/assets/selectize/selectize.min.js"></script> <script> $(function() { $("#publish_date").pickadate({ format: "mmm-d-yyyy" }); $("#publish_time").pickatime({ format: "h:i A" }); $("#tags").selectize({ create: true }); }); </script> @stop
咱們建立好edit視圖後要生成對應的方法:PostController下的edit方法。
public function edit($id) { $data = $this->dispatch(new PostFormFields($id)); return view('admin.post.edit', $data); }
php artisan make:request PostUpdateRequest
咱們的修改用的Request和建立用的Request很相像,因此直接繼承就好:
class PostUpdateRequest extends PostCreateRequest { }
public function update(Requests\PostUpdateRequest $request, $id) { $data = $request->postFillData(); $post = Post::findOrFail($id); $post->fill($data); $post->save(); $post->syncTags($request->get('tags', [])); if ($request->get('action') === 'continue'){ return redirect()->back()->withSuccess('Post saved.'); } return redirect()->route('admin.post.index')->withSuccess('Post saved.'); }
修改destroy方法:
public function destroy($id) { $post = Post::findOrFail($id); // 刪除中間表的數據。 $post->tags()->detach(); $post->delete(); return redirect() ->route('admin.post.index') ->withSuccess('Post deleted.'); }