Vue3×Vite開発サーバーのポート番号を変更する方法
開発中のVue 3プロジェクトで、ローカル環境で開発サーバーを起動すると、デフォルトではポート番号が http://localhost:3000/ のように設定されます。しかし、私の場合他のプロセスで使用しているのかhttp://localhost:5173/となっていました。
このポート番号が気になる場合や、既に使用されている別のポート番号を利用したい場合には、簡単に変更することができます。
Vue3プロジェクトの設定ファイルである vite.config.js を編集します。以下の手順でポート番号を変更することができます。
- vite.config.js ファイルを開きます。
- server オプション内に port プロパティを追加します。例えば、ポート番号を 3000 に変更する場合は、以下のように設定します
- ファイルを保存し、開発サーバーを再起動します。
- これで開発サーバーは新しいポート番号で起動されます。たとえば、ポート番号を 3000 に変更した場合、アプリケーションには http://localhost:3000/ でアクセスすることができます。
vite.config.js
export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { port: 3000 } })
注意点として、変更したポート番号がすでに使用されている場合や、他のアプリケーションと競合する可能性がある場合は、別のポート番号を選択する必要があります。
以上が、Vue 3開発サーバーのポート番号を変更する方法です。
Vue.js + Viteの環境構築してみた!
- 公式ドキュメントを参照しながらVue.jsの環境構築を行う。
今回は、Vue.jsに触れるはじめの一歩として公式サイトを参考にしながら、環境構築を進めていきます。
前提条件
- コマンドラインに精通していること
- Node.jsのバージョン16.0以上をインストールすること
以下のコマンドをコマンドラインで実行する。
>npm init vue@latest
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
Scaffolding project in ./<your-project-name>...
Done.
いったんはっきりと分からなかったので公式ドキュメントに従い全て「No」を選択。
※今回は、公式ドキュメントを参照しながら作業をしているのでファイル名は「document」としました。
続いて、以下を順番に実行する。
> cd document
> npm install
すると早速、警告!!
npm WARN deprecated sourcemap-codec@1.4.8: Please
use@jridgewell/sourcemap-codec instead
こちらのエラーの原因は、sourcemap-codecのパッケージが廃止されたことによる警告とのこと。
jridgewell/sourcemap-codecはsourcemap-codecの後継であり、同様の機能を提供しているので、以下のコマンドより解決。
npm install --save-dev @jridgewell/sourcemap-codec
無事にインストールが完了した後に以下のコマンドを実行。
> npm install
> npm run dev
無事にVueを起動することができました!!
引き続き作業を進めながら情報発信していきたいと思います。
GA4でボタンクリック数の測定をしてみた
目次
前置き
ボタンクリック数の測定する方法は、gtagで測定する方法とタグマネージャーで測定する方法がありますが今回は、gtagで測定する方法を紹介します。
※アナリティクスのアカウントが作成されていることを前提としています。
WEB上への設置
Googleタグの設置
以下は、このアカウントの Google タグです。このタグをコピーして、ウェブサイトのすべてのページのコード(<head> 要素の直後)に貼り付けます。各ページに複数の Google タグを実装することはできません。
<!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-◯◯◯◯◯◯"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-◯◯◯◯◯◯'); </script>
取得したいボタンにgtagを設置する
今回のパラメーターは、「event_category」と「button_name」としていますが自身のサイトにあったパラメーター設定で良いかと思います。
<a href=”/” onclick="gtag('event', 'user_info', {'event_category':'ユーザー画面に遷移', 'button_name':'ユーザー情報ボタン'});">ユーザー情報</a>
アナリティクス及びgtag設置後24〜48時間以内でデータが取れることが確認できます。
終わりに
業務で初めてアナリティクスの設定を行いましたので、アウトプットとして記事にさせていただきました。
メールアドレス、姓名、ipアドレスなどのデータ取得設定なども行いましたが、URLやボタンの状態によっては、システム上でアナリティクスのカスタムが必要になるものもあるので良い経験となりました。
Laravelで前月、次月へ遷移できるカレンダーを実装
目次
実装内容
bladeファイルでカレンダーが表示され、矢印ボタンを押下することで、前月もしくは次月へ遷移することができる。
開発環境
開発の流れ
- コントローラーの作成
- ビューの作成
- ルートの作成
コントローラーの作成
app/Http/Controllers/CalendarController.php
public function index($year = null, $month = null) { $weeks = ['日', '月', '火', '水', '木', '金', '土']; $carbon = new Carbon(); $carbon->locale('ja_JP'); if ($year) { $carbon->setYear($year); } if ($month) { $carbon->setMonth($month); } $carbon->setDay(1); $carbon->setTime(0, 0); $firstDayOfMonth = $carbon->copy()->firstOfMonth(); $lastOfMonth = $carbon->copy()->lastOfMonth(); $firstDayOfCalendar = $firstDayOfMonth->copy()->startOfWeek(); $endDayOfCalendar = $lastOfMonth->copy()->endOfWeek(); $dates = []; while ($firstDayOfCalendar < $endDayOfCalendar) { $dates[] = $firstDayOfCalendar->copy(); $firstDayOfCalendar->addDay(); } return view('index', compact('weeks', 'dates', 'firstDayOfMonth')); }
ビューの作成
resources/views/index.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Laravelでカレンダー表示</title> <link rel="stylesheet" href="{{ asset('common/css/style.css') }}"> <link href="{{ asset('common/css/bootstrap/bootstrap.min.css') }}" rel="stylesheet"> </head> <body> <div class="calendar_box container"> <div class="calendar-head"> <a class="arrow-back" href="{{ route('calendar.index', ['year' => $firstDayOfMonth->copy()->subMonth()->year, 'month' => $firstDayOfMonth->copy()->subMonth()->month]) }}"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-circle-fill" viewBox="0 0 16 16"> <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.5 7.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/> </svg> </a> <p class="current_month"><em>{{$firstDayOfMonth->format('Y')}}</em>年<em>{{$firstDayOfMonth->format('n')}}</em>月</p> <a class="arrow-next" href="{{ route('calendar.index', ['year' => $firstDayOfMonth->copy()->addMonth()->year, 'month' => $firstDayOfMonth->copy()->addMonth()->month]) }}"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-circle-fill" viewBox="0 0 16 16"> <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z"/> </svg> </a> </div> <table class="calendar"> <tr> @foreach ($weeks as $i => $week) <th @if ($i === \Carbon\Carbon::SUNDAY) class="sun" @elseif ($i === \Carbon\Carbon::SATURDAY) class="sat" @endif> {{ $week }} </th> @endforeach </tr> <tr> @foreach ($dates as $arr => $date) @if ($date->isSunday()) <tr> @endif <td @if ($date->isSunday()) class="sun" @elseif($date->isSaturday()) class="sat" @endif> @if ($arr == 0 || $date->format('j') == 1) <span>{{ $date->format('n/j') }}</span> @else <span>{{ $date->format('j') }}</span> @endif </td> @if ($date->isSaturday()) </tr> @endif @endforeach </table> </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="{{ asset('common/js/bootstrap/bootstrap.min.js') }}"></script> </body> </html>
ルートの作成
routes/web.php
Route::get('/', [CalendarController::class, 'index'])->name('index'); Route::get('calendar/{year?}/{month?}', [CalendarController::class, 'index'])->name('calendar.index');
終わりに
今回は、Laravelで前月、次月へ遷移できるカレンダーの実装を行いました。
未実装ですが、他のテーブルを用意することで入力されたデータをカレンダーに表示することもできます。参考になればと思います!
Laravelを使ってアンケート機能を作ってみた!!
目次
アンケート機能の内容
1つの投稿記事に対して、5つまでアンケートを設定可能。投稿のアンケートに回答することができ、回答された内容がデータベースに保存されます。
- 完成イメージ
- データベース保存イメージ
- ER図
開発の流れ
データベース周辺の作成
php artisan make:model Article -m
php artisan make:model Answer -m
database/migrations/****_**_**_******_create_articles_table.php
public function up() { Schema::create('articles', function (Blueprint $table) { $table->id(); $table->text('article_title'); $table->text('article_detail')->nullable(); $table->json('questionnaire_1')->nullable(); $table->json('questionnaire_2')->nullable(); $table->json('questionnaire_3')->nullable(); $table->json('questionnaire_4')->nullable(); $table->json('questionnaire_5')->nullable(); $table->timestamps(); });
questionnaireカラムは、アンケートの「タイトル」「内容」「inputタイプ」が配列で保存されるため、jsonとしています。
database/migrations/****_**_**_******_create_answers_table.php
public function up()
{ Schema::create('answers', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('article_id')->nullable(); $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade'); $table->text('answer_1')->nullable(); $table->text('answer_2')->nullable(); $table->text('answer_3')->nullable(); $table->text('answer_4')->nullable(); $table->text('answer_5')->nullable(); $table->timestamps(); });
モデルの作成
app/Models/Article.php
class Article extends Model { use HasFactory; // アンケート内容は、配列でデータベースに保存する protected $casts = [ 'questionnaire_1' => 'array', 'questionnaire_2' => 'array', 'questionnaire_3' => 'array', 'questionnaire_4' => 'array', 'questionnaire_5' => 'array' ]; public function answers() { return $this->hasMany(Answer::class); } public function getQRepeatAttribute() { $blocks = collect(); // 各アンケートを[collect]に入れる for ($i=1; $i <= 5; $i++) { $questionnaire = "questionnaire_".$i; // [type]と[title]が空ではない場合はcollectに代入する if (!empty($this->$questionnaire['q_type']) && !empty($this->$questionnaire['q_title'])) { ${'questionnaire_'.$i} = $this->$questionnaire; $blocks = $blocks->merge([${'questionnaire_'.$i}]); } } return collect($blocks); } }
bladeファイルでは、foreachだけで表示できるようにcollectで成形しています。
app/Models/Answer.php
class Answer extends Model { use HasFactory; protected $fillable = ['article_id']; public function article() { return $this->belongsTo(Article::class); } public function getAnswerArrayAttribute() { for ($i=1; $i <= 5; $i++) { $answer = "answer_$i"; if (!empty($this->$answer)) { $answer_array[] = explode(",",$this->$answer); } } return $answer_array; } }
コントローラーの作成
app/Http/Controllers/ArticleController.php
class ArticleController extends Controller { // 投稿記事作成画面 public function index() { return view('article.index'); } // 投稿記事詳細画面 public function detail($detail) { $article = Article::find($detail); return view('article.detail', compact('article')); } // 投稿記事作成処理 public function create(Request $request) { $article = new Article; $article->article_title = $request->article_title; $article->article_detail = $request->article_detail; for ($i=1; $i <= 5; $i++) { ${'questionnaire_'.$i} = array(); ${'questionnaire_'.$i} = array( "q_type" => $request->input("q{$i}"), "q_title" => $request->input("q_title_{$i}"), "q_select" => $request->input("q_select_{$i}") ); $questionnaire = "questionnaire_".$i; ${'questionnaire_'.$i}["q_select"] = explode(",", ${'questionnaire_'.$i}["q_select"]); $article->$questionnaire = ${'questionnaire_'.$i}; !$request->input("q_title") ?: "questionnaire_".$i = $questionnaire; } $article->save(); return redirect()->route('article.detail', ['detail' => $article->id])->with('flash_message', '投稿が完了しました。'); } // アンケートの回答を保存する処理 public function answer(Request $request, $article) { $answer = new Answer; for ($i=1; $i <= 5; $i++) { $answer_num = "answer_".$i; $request->$answer_num ? $answer_num = implode(",", $request->$answer_num) : $answer_num = null; $answer_str = "answer_".$i; $answer->$answer_str = $answer_num; } $answer->article_id = $article; $answer->save(); return redirect()->route('article.detail', ['detail' => $article])->with('flash_message', 'アンケートを回答しました'); } }
投稿記事、アンケートの回答などの各処理をArticleControllerで実装しています。
未実装ですがアンケートの回答をCSVで出力する想定でしたので、アンケートの回答は、配列ではなく文字列で保存する実装としています。
ビューの作成
resources/views/index.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body class="bg-dark"> <div class="container"> <div class="main container-fluid"> <div class="row bg-light text-dark py-5"> <div class="col-md-8 offset-md-2"> <h2 class="fs-1 mb-5 text-center fw-bold">投稿入力フォーム</h2> <form method="post" action="{{ route('article.create') }}"> @csrf <p>記事フォーム</p> <hr> <div class="mb-3"> <input type="text" class="form-control" name="article_title" placeholder="記事タイトル" value="" required> </div> <div class="mb-4"> <textarea class="form-control" name="article_detail" rows="5" placeholder="記事内容" required></textarea> </div> @for ($i = 1; $i < 6; $i++) <div class="questionnair-input{{$i}}" @if($i >= 2) style="display: none;" @endif> <p>アンケート内容記述フォーム{{$i}}</p> <hr> <div class="mb-3"> <input style="margin-right: 5px;" type="radio" id="radio{{$i}}" name="q{{$i}}" value="radio"><label style="margin-right: 20px;" for="radio{{$i}}">ラジオボタン</label> <input style="margin-right: 5px;" type="radio" id="checkbox{{$i}}" name="q{{$i}}" value="checkbox"><label style="margin-right: 20px;" for="checkbox{{$i}}">チェックボックス</label> <input style="margin-right: 5px;" type="radio" id="text{{$i}}" name="q{{$i}}" value="text"><label for="text{{$i}}">テキストフォーム</label> </div> <div class="mb-3"> <input type="text" class="form-control" name="q_title_{{$i}}" placeholder="アンケートタイトル" value=""> </div> <div class="mb-3"> <input type="text" class="form-control" name="q_select_{{$i}}" placeholder="アンケート選択肢" value=""> </div> @if ($i < 5) <div class="text-left pb-2 mb-3 col-md-12"> <button type="button" class="btn btn-success add-button{{$i}}">追加</button> </div> @endif </div> @endfor <div class="text-center pt-4 col-md-6 offset-md-3"> <button type="submit" class="btn btn-primary">送信</button> </div> </form> </div> </div> </div> </div> </body> <script> for (let i = 1; i < 5; i++) { document.querySelector('.add-button' + i).onclick = function() { var questionnair_from = document.getElementsByClassName('questionnair-input' + (i + 1)); questionnair_from[0].style.display = "block"; } }; </script> </html>
resources/views/article/index.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body class="bg-dark"> <div class="container"> <div class="main container-fluid"> <div class="row bg-light text-dark py-5"> <div class="col-md-8 offset-md-2"> <h2 class="fs-1 mb-5 text-center fw-bold">投稿記事</h2> <form method="post"> @csrf <p>記事フォーム</p> <hr> <div class="mb-3"> <input type="text" class="form-control" name="article" placeholder="記事タイトル" value=""> </div> <div class="mb-4"> <textarea class="form-control" name="お問い合わせ内容" rows="5" placeholder="記事内容"></textarea> </div> @for ($i = 1; $i < 6; $i++) <div class="questionnair-input{{$i}}" @if($i >= 2) style="display: none;" @endif> <p>アンケートフォーム{{$i}}</p> <hr> <div class="mb-3"> <input style="margin-right: 5px;" type="radio" id="radio{{$i}}" name="q{{$i}}" value="radio"><label style="margin-right: 20px;" for="radio{{$i}}">ラジオボタン</label> <input style="margin-right: 5px;" type="radio" id="checkbox{{$i}}" name="q{{$i}}" value="checkbox"><label style="margin-right: 20px;" for="checkbox{{$i}}">チェックボックス</label> <input style="margin-right: 5px;" type="radio" id="text{{$i}}" name="q{{$i}}" value="text"><label for="text{{$i}}">テキストフォーム</label> </div> <div class="mb-3"> <input type="text" class="form-control" name="article" placeholder="アンケートタイトル" value=""> </div> <div class="mb-3"> <input type="text" class="form-control" name="article" placeholder="アンケート選択肢" value=""> </div> @if ($i < 5) <div class="text-left pb-2 mb-3 col-md-12"> <button type="button" class="btn btn-success add-button{{$i}}">追加</button> </div> @endif </div> @endfor <div class="text-center pt-4 col-md-6 offset-md-3"> <button type="submit" class="btn btn-primary">送信</button> </div> </form> </div> </div> </div> </div> </body> </html>
resources/views/article/detail.blade.php
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> <style type="text/css"> form { background-color: #fff; padding: 20px; } </style> </head> <body class="bg-dark"> <div class="container"> <div class="main container-fluid"> <div class="bg-light text-dark py-5"> <div class="col-md-8 offset-md-2"> <h2 class="fs-1 mb-5 text-center fw-bold">投稿記事詳細</h2> @if (session('flash_message')) <div class="comment_bnr comment_submit alert-success"> <p>{{ session('flash_message') }}</p> </div> @endif <p>投稿記事</p> <hr> <p>{{ $article->article_title }}</p> <p>{{ $article->article_detail }}</p> @if ($article->q_repeat->isNotEmpty()) <form method="post" action="{{route('article.answer', ['article' => $article])}}"> @csrf <p>アンケートフォーム</p> <hr> @php $counter = 1; @endphp @foreach ($article->q_repeat as $questionnaire) <div class="mb-3"> <p>{{$questionnaire['q_title']}}</p> @for ($i = 0; $i < count($questionnaire['q_select']); $i++) <label> <input type="{{$questionnaire['q_type']}}" name="answer_{{$counter}}[]" value="{{$questionnaire['q_select'][$i]}}"> {{$questionnaire['q_select'][$i]}} </label> @endfor </div> <hr> @php $counter ++; @endphp @endforeach <div class="text-center pt-4 col-md-6 offset-md-3"> <a href="{{route('index')}}"><button type="button" class="btn btn-success"> 投稿フォームへ戻る</button></a> </div> <div class="text-center pt-4 col-md-6 offset-md-3"> <button type="submit" class="btn btn-primary">送信</button> </div> </form> @endif </div> </div> </div> </div> </body> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script> // フラッシュメッセージのfadeout $(function(){ setTimeout("$('.comment_submit').fadeOut('slow')", 3000); }); </script> </html>
ルートの作成
resources/views/article/detail.blade.php
Route::view('/', 'index')->name('index'); Route::group(['prefix' => 'article'], function() { Route::get('/{detail}', [ArticleController::class, 'detail'])->name('article.detail'); Route::post('/create', [ArticleController::class, 'create'])->name('article.create'); Route::post('/{article}/answer', [ArticleController::class, 'answer'])->name('article.answer'); });
終わりに
今回は、同様のアンケート機能を実務で、実装したのでアウトプットと復習の意味を込めて記事を書かせていただきました。まだまだ、わからないところだらけでうまく実装できていない箇所もあると思いますが、参考になればと思います!
【Mac】Sourcetreeのカスタムができない?を解決する方法!!
発生している問題
Sourcetreeの「環境設定」から「カスタムアクション」が選択できないため、カスタム設定ができない。
バージョン情報
macOS Monterey バージョン 12.6
MacBook Air (M1, 2020)
Sorcetreeバージョン 4.1.8(244)
解決方法1
- Soucetreeのアプリケーションを開き、画面上で右クリック。
- 「カスタムアクション」を選択することで設定が可能となる。
解決方法2
- Sourcetreeの「環境設定」から「一般」へ移動する。言語設定を「日本語」から「English(U.S.)」などに変更して再起動を行う。
デメリット
解決方法1:「環境設定」から「カスタムアクション」が選択できない
解決方法2:日本語以外での操作
終わりに
再インストールやSourcetreeのバージョンを下げることで対処する方法もあるが、時間がない場合や「日本語以外での操作」、「カスタムボタンが選択できなくても気にならない」場合は、こちらの記事でSourcetreeをカスタムすることができます。