3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LaravelとVue.jsを使った見積作成アプリ その3

Last updated at Posted at 2021-03-04

##前回の復習
前回はVue.jsを使用し見積編集ページのテンプレートを作成しました。今回はdompdfを利用したpdf表示ページとユーザー認証関係のページを作成していきます。

##dompdfの導入
laravel-dompdfパッケージのインストールはcomposerを使用して行います。

$ composer require barryvdh/laravel-dompdf

config/app.phpを以下のように編集します。
providersに下記を追記します。

app.php
Barryvdh\DomPDF\ServiceProvider::class,

aliasesに下記を追記します。

app.php
'PDF' => Barryvdh\DomPDF\Facade::class,

###コントローラーの作成
PDFの作成を行うため、コマンドを使ってコントローラーを作成します。

$ php artisan make:controller PDFController

作成されたコントローラーに記述していきます。

PDFController.php
<?php

namespace App\Http\Controllers;

use App\Estimate;
use App\Item;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use PDF;

class PDFController extends Controller
{
    public function index(Request $request){
        $user = Auth::user();
        $estimate_id = $request->input('estimate');
        $estimate = Estimate::find($estimate_id);
        $estimated_at = $estimate->estimated_at;
        $items = Item::where('estimate_id', $estimate_id)->orderBy('id')->get();
        $item_price = Item::where('estimate_id', $estimate_id)->get(['quantity', 'unit_price']);
        $sum_price = 0;

        if($user->id != $estimate->user_id) {
            return redirect()->route('estimates.index');
        }

        for($i=0; $i<count($item_price); $i++){
            $calculation_price = $item_price[$i]['quantity'] * $item_price[$i]['unit_price'];
            $sum_price += $calculation_price;
        }

    	$pdf = PDF::loadView('pdf/generate_pdf', [
            'user' =>$user,
            'estimate' => $estimate,
            'estimated_at' => date('Y年m月d日', strtotime($estimated_at)),
            'items' => $items,
            'sum_price' => $sum_price,
        ]);

        return $pdf->stream();
    }
}

loadView() を利用することで、通常のビューを用意するのと同じ手順で、PDFに出力したい内容をHTMLで記述することができるようになります。変数を渡すことも可能です。

###ルーティングの作成

web.php
Route::get('pdf','PDFController@index');

これでテンプレートを作成すればPDFが表示されます。

###日本語化
PDFの作成は可能になりましたが、このままだと日本語が文字化けしてしまいます。日本語のPDFが作成できるように設定を行います。

###fontsディレクトリの作成

strageディレクトリの下にfontsディレクトリを作成します。

$ mkdir fonts

###IPAフォントのダウンロード

以下のサイトからIPAフォントをダウンロードします。
https://moji.or.jp/ipafont/

zipファイルとしてダウンロードされるので、解凍後、その中にあるファイルを先ほど作成したstorage/fonts/ディレクトリの下にコピーしてください。

これで無事日本語に対応しましたのでテンプレートを作成していきます。

###PDFテンプレートの作成

views/pdf/generate_pdf.blade.phpを作成し記述していきます。

generate_pdf.blade.php
<!doctype html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <style type="text/css">
        @font-face {
            font-family: ipam;
            font-style: normal;
            font-weight: normal;
            src: url('{{ storage_path('fonts/ipam.ttf') }}') format('truetype');
        }
        @font-face {
            font-family: ipam;
            font-style: bold;
            font-weight: bold;
            src: url('{{ storage_path('fonts/ipam.ttf') }}') format('truetype');
        }
        body {
            font-family: ipam !important;
        }
        .box {
          width        : 50px;
          height       : 50px;
          background   : white;
          border       : medium solid #000000;
          position: absolute;
        }
    </style>
</head>
<body>
  <h1 style="text-align:center;">御見積書</h1>
  <div style="float:left">
    <h2>{{ $estimate['customer'] }}様</h2>
    <p>下記の通り御見積申し上げます</p>
    <p>件名:{{ $estimate['title'] }}</p>
    <p>納入期限:{{ $estimate['deadline_at'] }}</p>
    <p>納入場所:{{ $estimate['location'] }}</p>
    <p>取引方法:{{ $estimate['transaction'] }}</p>
    <p>有効期限:{{ $estimate['effectiveness'] }}</p>
    <u><h2>御見積合計金額 ¥{{ number_format($sum_price * 1.1) }}-</h2></u>
  </div>
  <div style="float:right">
    <p>見積日 {{ $estimated_at }}</p>
    <p style="padding-top:25px">〒{{ $user['postal_code'] }}</p>
    <p>{{ $user['address'] }}</p>
    <p>{{ $user['address2'] }}</p>
    <p>{{ $user['company'] }}</p>
    <p>TEL: {{ $user['phone_number']}}  FAX: {{ $user['fax_number'] }}</p>
    <p>担当者: {{ $user['name'] }}</p>
  </div>
  <p class="box" style="margin-top:375px;margin-left: 547px;"></p>
  <a class="box" style="margin-top:375px;margin-left: 597px;"></a>
  <a class="box" style="margin-top:375px;margin-left: 647px;"></a>
  <div style="margin-top:360px">
    <table border="1" width="100%" cellspacing="0" cellpadding="0" style="table-layout: auto">
      <tr>
        <th>商品名</th>
        <th>単位</th>
        <th>数量</th>
        <th>単価</th>
        <th>金額</th>
        <th>備考</th>
      </tr>
      @foreach ($items as $item)
        <tr>
          <td>
            {{ $item['name'] }}
          </td>
          <td style="text-align:center">
            {{ $item['unit'] }}
          </td>
          <td style="text-align:right">
            {{ $item['quantity'] }}
          </td>
          <td style="text-align:right">
            {{ number_format($item['unit_price']) }}
          </td>
          <td style="text-align:right">
            {{ number_format($item['quantity'] * $item['unit_price']) }}
          </td>
          <td style="font-size:12px">
            {{ $item['other'] }}
          </td>
        </tr>
      @endforeach
      <tr>
        <td style="text-align:right"><税抜合計金額></td>
        <td></td>
        <td></td>
        <td></td>
        <td style="text-align:right">{{ number_format($sum_price) }}</td>
        <td></td>
      </tr>
      <tr>
        <td style="text-align:right"><消費税></td>
        <td></td>
        <td></td>
        <td></td>
        <td style="text-align:right">{{ number_format($sum_price * 0.1) }}</td>
        <td></td>
      </tr>
      <tr>
        <td>毎度ありがとうございます。</td>
        <td></td>
        <td></td>
        <td style="text-align:center">合計</td>
        <td style="text-align:right">{{ number_format($sum_price * 1.1) }}</td>
        <td></td>
      </tr>
    </table>
  </div>
</body>
</html>

headタグの@font-faceでは使用するフォントの設定を行っています。storage_pathにはIPAフォントを保存したstorage/fontsを指定します。
font-style:normalだけでは、h1タグのようなfont-weightがboldに設定されているフォントに文字化けが発生します。上記のようにfont-faceを複数設定することで対応できます。

これでPDF表示ページが完成しました。

##認証機能

###ユーザーと見積を紐付ける
認証機能の追加にともなって、データ構造としてはまずユーザーが存在して、ユーザーごとに自分の見積もりを作っていく形にしたいと思います。

###マイグレーション

$ php artisan make:migration add_user_id_to_estimates --table=estimates

作成されたファイルを編集していきます。

add_user_id_to_estimates.php
<?php

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

class AddUserIdToEstimates extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('estimates', function (Blueprint $table) {
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('estimates', function (Blueprint $table) {
            $table->dropColumn('user_id');
        });
    }
}

upメソッドではuser_idカラムを追加して外部キーを設定する処理を記述しています。downメソッドでは逆にuser_idカラムを削除する処理を記述しています。
マイグレーションを実行しましょう。

$ php artisan migrate:fresh

次にユーザーと見積の関係性をモデルに記述していきます。

User.php
class User extends Authenticatable
{
    // 中略

    public function folders()
    {
        return $this->hasMany('App\Folder');
    }
}

###シーダーの作成
ユーザーのシーダーを作成しましょう。

$ php artisan make:seeder UsersTableSeeder

database/seeds/UsersTableSeeder.php が作成されるので、以下の内容で編集します。

UserTableSeeder.php
<?php

use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => 'test',
            'email' => 'dummy@email.com',
            'password' => bcrypt('test1234'),
            'created_at' => Carbon::now(),
            'updated_at' => Carbon::now(),
        ]);
    }
}

データベース上、ユーザーのパスワードは必ず暗号化してデータベースに保存します。平文では保存しません。bcrypt関数は与えられた文字列の暗号化を行います。

次に見積テーブル用のシーダーを編集します。星マークが追加した行です。

EstimateTableSeeder.php
<?php

use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class EstimateTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $user = DB::table('users')->first(); // ★

        $titles = ['2021年おめでとうセール', '商品見積の件', 'サンプル見積の件'];
        $customers = ['株式会社XXX', '株式会社YYY', '株式会社ZZZ'];

        foreach (array_map(NULL, $titles, $customers) as [ $title, $customer ]) {
            DB::table('estimates')->insert([
                'title' => $title,
                'user_id' => $user->id, // ★
                'customer' => $customer,
                'created_at' => Carbon::now(),
                'updated_at' => Carbon::now(),
            ]);
        }
    }
}

###会員登録機能
Laravelには認証機能が最初から搭載されています。認証機能を受け持つコントローラーはapp/Http/Controllers/Authディレクトリにすでに用意されています。ルーティングについても認証用の設定を吐き出すメソッドが用意されているので、基本的にはテンプレートを作成するだけでアプリケーションに認証機能を追加することができます。

###ルーティング

web.php
Auth::routes();

この1行を記述するだけで、会員登録・ログイン・ログアウト・パスワード再設定の各機能で必要なルーティング設定をすべて定義します。

###テンプレート

以下の内容で resources/views/auth/register.blade.phpを作成します。

register.blade.php
@extends('layout')

@section('content')
  <div class="container">
    <div class="row">
      <div class="col col-md-offset-3 col-md-6">
        <nav class="panel panel-default">
          <h2 class="panel-heading" style="padding-top:25px">会員登録</h2>
          <div class="panel-body">
            @if($errors->any())
              <div class="alert alert-danger">
                @foreach($errors->all() as $message)
                  <p>{{ $message }}</p>
                @endforeach
              </div>
            @endif
            <form action="{{ route('register') }}" method="POST">
              @csrf
              <div class="form-group">
                <label for="email">メールアドレス</label>
                <input type="text" class="form-control" id="email" name="email" value="{{ old('email') }}" />
              </div>
              <div class="form-group">
                <label for="name">ユーザー名</label>
                <input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}" />
              </div>
              <div class="form-group">
                <label for="password">パスワード</label>
                <input type="password" class="form-control" id="password" name="password">
              </div>
              <div class="form-group">
                <label for="password-confirm">パスワード(確認)</label>
                <input type="password" class="form-control" id="password-confirm" name="password_confirmation">
              </div>
              <div class="text-right">
                <button type="submit" class="btn btn-dark">送信</button>
              </div>
            </form>
            <div class="text-center" style="padding-top:25px">
              <a href="{{ route('login.guest') }}"><button class="btn btn-dark">ゲストユーザーとしてログイン</button></a>
            </div>
          </div>
        </nav>
      </div>
    </div>
  </div>
@endsection

次にログイン機能を実装します。

resources/views/auth/login.blade.phpを以下の内容で作成します。

login.blade.php
@extends('layout')

@section('content')
  <div class="container">
    <div class="row">
      <div class="col col-md-offset-3 col-md-6">
        <nav class="panel panel-default">
          <h2 class="panel-heading" style="padding-top:25px">ログイン</h2>
          <div class="panel-body">
            @if($errors->any())
              <div class="alert alert-danger">
                @foreach($errors->all() as $message)
                  <p>{{ $message }}</p>
                @endforeach
              </div>
            @endif
            <form action="{{ route('login') }}" method="POST">
              @csrf
              <div class="form-group">
                <label for="email">メールアドレス</label>
                <input type="text" class="form-control" id="email" name="email" value="{{ old('email') }}" />
              </div>
              <div class="form-group">
                <label for="password">パスワード</label>
                <input type="password" class="form-control" id="password" name="password" />
              </div>
              <div class="text-right">
                <button type="submit" class="btn btn-dark">ログイン</button>
              </div>
            </form>
          </div>
        </nav>
        <div class="text-center" style="padding-top:25px">
          <a href="{{ route('login.guest') }}"><button class="btn btn-dark">ゲストユーザーとしてログイン</button></a>
        </div>
      </div>
    </div>
  </div>
@endsection

これでログイン機能の実装は完了です。

##プロフィール編集ページ

最後に見積に表示するためのプロフィール編集ページを作成します。

###コントローラーの作成
プロフィール編集ページを作成するためのユーザーコントローラーを作成します。

$ php artisan make:controller ItemController
UserController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class UserController extends Controller
{
    public function showEditForm() 
    {
        $auth = Auth::user();

        return view('auth/edit',[
            'auth' => $auth,
        ]);
    }

    public function edit(Request $request)
    {
        $current_user = Auth::user();

        $current_user->name = $request->name;
        $current_user->postal_code = $request->postal_code;
        $current_user->address = $request->address;
        $current_user->address2 = $request->address2;
        $current_user->company = $request->company;
        $current_user->phone_number = $request->phone_number;
        $current_user->fax_number = $request->fax_number;
        
        $current_user->save();
        return redirect()->route('estimates.index');
    }
}

Auth::user()でユーザーの情報を取得しユーザー情報を表示・編集します。

###ルーティングの追加

web.php
Route::group(['middleware' => 'api'], function(){
    Route::get('/user/edit', 'UserController@showEditForm')->name('user.edit');
    Route::post('/user/edit', 'UserController@edit');
});

あとはテンプレートを作成するだけです。

###ユーザー編集ページのテンプレートを作成

@extends('layout')

@section('content')
  <div class="container">
    <h2 class="panel-heading" style="padding-top:25px">ユーザー情報編集</h2>
    <form action="{{ route('user.edit')}}" method="post">
      @csrf
      <div class="row">
        <div class="col-sm-2">ユーザー名</div>
        <div class="col-sm-10" style="padding: 3px;">
          <input type="text" name="name" value="{{$auth->name}}">
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">郵便番号</div>
        <div class="col-sm-10" style="padding: 3px;">
          <input type="text" name="postal_code" value="{{$auth->postal_code}}">
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">住所</div>
        <div class="col-sm-10" style="padding: 3px;">
          <textarea name="address" value="{{$auth->address}}">{{$auth->address}}</textarea>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">ビル・マンション名</div>
        <div class="col-sm-10" style="padding: 3px;">
          <textarea name="address2">{{$auth->address2}}</textarea>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">会社名</div>
        <div class="col-sm-10" style="padding: 3px;">
          <textarea name="company">{{$auth->company}}</textarea>
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">電話番号</div>
        <div class="col-sm-10" style="padding: 3px;">
          <input type="text" name="phone_number" value="{{$auth->phone_number}}">
        </div>
      </div>
      <div class="row">
        <div class="col-sm-2">FAX番号</div>
        <div class="col-sm-10" style="padding: 3px;">
          <input type="text" name="fax_number" value="{{$auth->fax_number}}">
        </div>
      </div>
      <div style="padding-top:25px;">
        <button type="submit" class="btn btn-dark">保存</button>
      </div>
    </form>
  </div>
@endsection

これで予定していた全ての機能が完成しました。

LaravelとVue.js共にまだまだ使いこなせてない機能がたくさんあるので勉強と開発を進めていこうと思います。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?