2021年1月5日 星期二

新建話題 upload

 新建話題  upload

==================================================
http://larabbs.test/topics/create

==================================================
*route:

/*
| GET|HEAD  | topics | topics.index | App\Http\Controllers\TopicsController@index | web
| POST      | topics | topics.store | App\Http\Controllers\TopicsController@store | web
| GET|HEAD  | topics/create | topics.create | App\Http\Controllers\TopicsController@create | web
| PUT|PATCH | topics/{topic} | topics.update | App\Http\Controllers\TopicsController@update | web
| DELETE    | topics/{topic} | topics.destroy | App\Http\Controllers\TopicsController@destroy | web
| GET|HEAD  | topics/{topic}/edit | topics.edit | App\Http\Controllers\TopicsController@edit | web
*/
Route::resource('topics', 'TopicsController', ['only' => ['index', 'create', 'store', 'update', 'edit', 'destroy']]);

Route::post('upload_image', 'TopicsController@uploadImage')->name('topics.upload_image');

==================================================
*controller:

app\Http\Controllers\TopicsController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests\TopicRequest;
use App\Models\Topic;
use App\Models\Category;
use App\Models\User;
use App\Models\Link;
use Auth;
use App\Handlers\ImageUploadHandler;

class TopicsController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth', ['except' => ['index', 'show']]);
    }
    
    //新建話題:view
    public function create(Topic $topic)
    {
        $categories = Category::all();
        return view('topics.create_and_edit', compact('topic', 'categories'));
    }

    //新建話題:儲存資料
    public function store(TopicRequest $request, Topic $topic)
    {
        $topic->fill($request->all());
        $topic->user_id = Auth::id();
        $topic->save();

        return redirect()->to($topic->link())->with('success', '建立話題成功!');
    }

    //新建話題:上傳圖片
    public function uploadImage(Request $request, ImageUploadHandler $uploader)
    {
        //初始化返回資料,默認是失敗的
        $data = [
            'success' => false,
            'msg' => '上傳失敗!',
            'file_path' => '',
        ];

        //判斷是否有上傳檔案,並賦值給 $file
        if($file = $request->upload_file){
            //保存圖片到主機(本地端)
            $result = $uploader->save($request->upload_file, 'topics', \Auth::id(), 1024);

            //圖片保存成功
            if($result){
                $data['file_path'] = $result['path'];
                $data['msg'] = '上傳成功!';
                $data['success'] = true;
            }
        }

        return $data;
    }
}

---------------------------
app\Http\Requests\TopicRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TopicRequest extends FormRequest
{
    /**
     * 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()
    {   
        switch($this->method())
        {
            //CREATE
            case 'POST':
            //UPDATE
            case 'PUT':
            case 'PATCH':
            {
                return [
                    'title' => 'required|min:2',
                    'body' => 'required|min:3',
                    'category_id' => 'required|numeric',
                ];
            }
            case 'GET':
            case 'DELETE':
            default:
            {
                return [];
            };
        }
    }

    public function messages()
    {
        return [
            'title.min' => '標題必須填寫最少2個字',
            'body.min' => '文章內容必須填寫最少3個字',
        ];
    }
}

---------------------------
app\Handlers\ImageUploadHandler.php

<?php

namespace App\Handlers;

use Image;
use Str;

class ImageUploadHandler
{
    protected $allowed_ext = ["png", "jpg", "gif", "jpeg"];

    public function save($file, $folder, $file_prefix, $max_width = false)
    {
        //建立儲存圖片的資料夾規則,例如:uploads/images/avatars/202010/22/
        //資料夾切割可讓找尋圖片的效率更高
        $folder_name = "uploads/images/$folder/" . date('Ym/d', time());

        //儲存圖片的實際路徑( public_path() 是 public資料夾 的路徑 )
        //例如: /home/vagrant/code/larabbs/public/uploads/images/avatars/202010/22/
        $upload_path = public_path() . '/' . $folder_name;

        //獲取圖檔的副檔名 (若圖檔沒有副檔名,此處確保副檔名一直存在)
        //若圖檔有副檔名,則取其副檔名 ( $file->getClientOriginalExtension() )
        //若圖檔沒有副檔名,則設定為png
        $extension = strtolower($file->getClientOriginalExtension()) ?: 'png';
        // dd($extension);

        //連接檔案名稱,加前綴是為了增加辨識度,前綴可以是相關數據模型的 ID
        //例如:1_1493521050_7BVc9v9ujP.png
        $filename = $file_prefix . '_' . time() . '_' . Str::random(10) . '.' . $extension;

        //如果上傳的不是圖片,則終止操作
        if(! in_array($extension, $this->allowed_ext)){
            return false;
        }

        //把圖片移到自己設定的路徑
        $file->move($upload_path, $filename);

        //如果限制了圖片寬度,就執行圖片裁剪
        if($max_width && $extension != 'gif'){
            //此類別封裝的函數,用於執行圖片裁剪
            $this->reduceSize($upload_path . '/' . $filename, $max_width);
        }

        return [
            'path' => config('app.url') . "/$folder_name/$filename"
        ];
    }

    public function reduceSize($file_path, $max_width)
    {
        //先實例化,參數是圖片的路徑
        $image =Image::make($file_path);

        //調整圖片尺寸
        $image->resize($max_width, null, function($constraint){
            //設定寬度為 $max_width,高度為等比例縮放
            $constraint->aspectRatio();

            //防止裁圖時圖片尺寸變大
            $constraint->upsize();
        });

        //儲存已經調整完成的圖片
        $image->save();
    }
}

==================================================
*view:

resources\views\topics\create_and_edit.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="col-md-10 offset-md-1">
        <div class="card">
            <div class="card-body">
                <h2 class="">
                    <i class="far fa-edit"></i>
                    @if($topic->id)
                        編輯話題
                    @else
                        新建話題
                    @endif
                </h2>

                <hr>

                @if($topic->id)
                    <form action="{{ route('topics.update', $topic->id) }}" method="POST" accept-charset="UTF-8">
                        <input type="hidden" name="_method" value="PUT">
                @else
                    <form action="{{ route('topics.store') }}" method="POST" accept-charset="UTF-8">
                @endif
                        <input type="hidden" name="_token" value="{{ csrf_token() }}">
                        @include('shared._error')
                        <div class="form-group">
                            <input class="form-control" type="text" name="title" value="{{ old('title', $topic->title) }}" placeholder="請填寫標題" required>
                        </div>
                        <div class="form-group">
                            <select class="form-control" name="category_id" required>
                                <option value="" hidden disabled {{ $topic->id ? '' : 'selected' }}>請選擇分類</option>
                                @foreach($categories as $value)
                                    <option value="{{ $value->id }}" {{ $topic->category_id == $value->id ? 'selected' : '' }}>
                                        {{ $value->name }}
                                    </option>
                                @endforeach
                            </select>
                        </div>
                        <div class="form-group">
                            <textarea name="body" class="form-control" id="editor" rows="6" placeholder="請填寫至少3個字的內容" required>{{ old('body', $topic->body) }}</textarea>
                        </div>
                        <div class="well well-sm">
                            <button type="submit" class="btn btn-primary"><i class="far fa-save mr-2" aria-hidden="true"></i> 儲存</button>
                        </div>
                    </form>
            </div>
        </div>
    </div>
</div>
@endsection

@section('styles')
    <link rel="stylesheet" type="text/css" href="{{ asset('css/simditor.css') }}">
@stop

@section('scripts')
    <script type="text/javascript" src="{{ asset('js/module.js') }}"></script>
    <script type="text/javascript" src="{{ asset('js/hotkeys.js') }}"></script>
    <script type="text/javascript" src="{{ asset('js/uploader.js') }}"></script>
    <script type="text/javascript" src="{{ asset('js/simditor.js') }}"></script>

    <script>
        $(document).ready(function(){
            var editor = new Simditor({
                textarea: $('#editor'),
                upload: {
                    url: '{{ route('topics.upload_image') }}',
                    params: {
                        _token: '{{ csrf_token() }}'
                    },
                    fileKey: 'upload_file',
                    connectionCount: 3,
                    leaveConfirm: '檔案上傳中,關閉此頁面將取消上傳!'
                },
                pasteImage: true,
            });
        });
    </script>
@stop

------------------------
Route::post('upload_image', 'TopicsController@uploadImage')->name('topics.upload_image');

==================================================
*model:

app\Models\Topic.php

<?php

namespace App\Models;

class Topic extends Model
{
    protected $fillable = [
        'title', 'body', 'category_id', 'excerpt', 'slug'
    ];

    public function replies()
    {
        return $this->hasMany(Reply::class);
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function scopeWithOrder($query, $order)
    {
        //不同的排序,使用不同的數據讀取邏輯
        switch($order) {
            case 'recent':
                $query->recent();
                break;
            default:
                $query->recentReplied();
                break;
        }
    }

    public function scopeRecentReplied($query)
    {
        /*
            當話題有新回覆時,我們將編寫邏輯來更新話題模型的 reply_count 屬性,
            此時會自動觸發框架對數據模型 updated_at 時間戳的更新
        */
        return $query->orderBy('updated_at', 'desc');
    }

    public function scopeRecent($query)
    {
        //依照建立時間排序
        return $query->orderBy('created_at', 'desc');
    }

    public function link($params = [])
    {
        return route('topics.show', array_merge([$this->id, $this->slug], $params));
    }

    public function updateReplyCount()
    {
        $this->reply_count = $this->replies->count();
        $this->save();
    }
}

==================================================
*migration:

database\migrations\2020_11_02_165250_create_topics_table.php

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTopicsTable extends Migration
{
    public function up()
    {
        Schema::create('topics', function(Blueprint $table) {
            $table->increments('id');
            $table->string('title')->index();
            $table->text('body');
            $table->bigInteger('user_id')->unsigned()->index();
            $table->integer('category_id')->unsigned()->index();
            $table->integer('reply_count')->unsigned()->default(0);
            $table->integer('view_count')->unsigned()->default(0);
            $table->integer('last_reply_user_id')->unsigned()->default(0);
            $table->integer('order')->unsigned()->default(0);
            $table->text('excerpt')->nullable();
            $table->string('slug')->nullable();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('topics');
    }
}

==================================================

XHR
https://ithelp.ithome.com.tw/articles/10203820



沒有留言:

張貼留言