- 投稿日:2020-12-23T22:50:42+09:00
LaravelとVue.jsを使った見積作成アプリ その1
はじめに
この記事は私自身がLaravelとVue.jsを勉強する目的で学んだことをまとめたものです。
この記事で作成するアプリケーションについて
この記事は以下の環境で作成しました。
- Laravel 5.7.29
- PHP 7.3.11
- Vue.js 2.6.11
アプリケーションの全体像
今回作成する見積作成アプリは全部で6画面あり、認証機能まで備えたアプリケーションを作成していきます。
こちらが完成した見積作成アプリです。
- 見積一覧ページ
- このページでは作成した見積の一覧を表示します。
- 見積編集ページ
- このページで見積の内容を編集、保存します。
- PDF表示ページ
- このページでは作成した見積をPDFで表示し、保存と印刷を可能にします。
- ログインページ
- ログインページも作成します。最終的にはログイン中のユーザーの見積のみ表示するように実装します。
- 会員登録ページ
- 会員登録ではメールアドレス、ユーザー名、パスワードを入力します。
- プロフィール編集ページ
- このページで見積に表示される自分の情報を編集できるようにします。
テーブル定義
見積テーブルと商品テーブルを作成します。二つのテーブルの関係性は見積一つに対し商品が多数紐づく「一対多」にします。
見積テーブル ID id タイトル title 納入場所 location 取引方法 transaction 有効期限 effectiveness 宛先 customer 納入期限 deadline_at 見積日 estimated_at
商品テーブル ID id 見積ID estimate_id 商品名 name 単位 unit 数量 quantity 単価 unit_price 備考 other 見積一覧ページの作成
環境構築ができていてLaravelの初期画面が表示されている前提で進めます。
データベースの接続設定
まずは接続設定を.envで行います。estimateというデータベースを作成しています。環境構築にはHomesteadを使用しました。
DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=estimate DB_USERNAME=homestead DB_PASSWORD=secretマイグレーションファイルとモデルクラスの作成
$ php artisan make:migration create_estimates_table --create=estimates作成されたファイルに記入していきます。
create_estimates_table.phpuse Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateEstimatesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('estimates', function (Blueprint $table) { $table->increments('id'); $table->string('title', 100)->nullable(); $table->string('location', 100)->nullable(); $table->string('transaction', 100)->nullable(); $table->string('effectiveness', 100)->nullable(); $table->string('customer', 100)->nullable(); $table->string('deadline_at', 100)->nullable(); $table->date('estimated_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('estimates'); } }テーブル名はestimatesとしました。格納したい物の名前の複数形にするのが一般的です。また、見積作成の途中で保存したい時や記入せずに作成したい場合に対応するため、nullable()でカラムにNULL値を許容しました。
マイグレーションを実行します。
$ php artisan migrate次にモデルクラスを作成します。
$ php artisan make:model EstimateappディレクトリにEstimateモデルが作成されます。
Estimate.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Estimate extends Model { // }Estimateモデルに記述はしていませんが、継承元であるModelクラスで様々な設定を読み取ってくれるらしいです。
これでデータを扱う準備ができたのですが、テストデータが入っていた方がコントローラーを書きやすいので、Seederを用いてデータを挿入します。
$ php artisan make:seeder EstimatesTableSeederrunメソッドの中にデータを挿入するコードを記述します。ここでは3つの見積を作りました。
EstimatesTableSeeder.php<?php use Carbon\Carbon; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class EstimatesTableSeeder 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(), ]); } } }コマンドラインで実行します。
$ php artisan db:seed --class=EstimatesTableSeeder「Database seeding completed successfully.」と返ってきたら成功です。
ルーティングの設定
web.phpRoute::get('/estimates', 'EstimateController@index')->name('estimates.index');コントローラークラスの作成
コントローラークラスはコマンドラインから作成。
$ php artisan make:controller EstimateController作成されたEstimateController.phpにindexメソッドを追加します。
EstimateController.phpuse App\Estimate; // ★ 追加 public function index() { $estimates = Estimate::all(); return view('estimates/index', [ 'estimates' => $estimates, ]); }view関数でテンプレートにデータを渡し、その結果を返却しています。view関数の第一引数がテンプレートファイル名で第二引数がテンプレートに渡すデータです。
テンプレートの作成
テンプレートファイルを作成します。
$ mkdir resources/views/estimates $ touch resources/views/estimates/index.blade.php $ touch resources/views/layout.blade.phplayout.blade.phpとindex.blade.phpの中身は以下のように記述しました。
layout.blade.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>見積作成アプリ</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <link rel="stylesheet" href="{{ asset('css/app.css') }}"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> </head> <body> <header> <nav class="navbar navbar-expand-xs navbar-dark bg-dark p-1"> <a class="navbar-brand" href="{{ route('estimates.index') }}">見積作成アプリ</a> </nav> </header> @yield('content') <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script> @yield('scripts') </body> </html>index.blade.php@extends('layout') @section('content') <main> <div class="container"> <div class="row"> <div class="col col-md-12"> <h2 class="text-center" style="padding-top:25px">見積一覧</h2> <table class="table table-bordered table-hover" style="table-layout:fixed;"> <thead class="thead-dark"> <tr> <th class="col">タイトル</th> <th class="col">見積もり期日</th> <th class="col">場所</th> <th class="col">宛先</th> </tr> </thead> <tbody> @foreach($estimates as $estimate) <tr> <td class="position-relative"> <a href="{{ route('estimates.edit', ['estimate' => $estimate->id]) }}" class="stretched-link"> {{ $estimate->title }} </a> </td> <td>{{ $estimate->estimated_at }}</td> <td>{{ $estimate->location }}</td> <td>{{ $estimate->customer }}</td> </tr> @endforeach </tbody> </table> </div> </div> </div> </main> <footer class="fixed-bottom bg-dark"> <nav class="my-navbar"> <div class="container"> <div class="row"> <div class="col-md-3"> <a href="#"> <button>新規作成</button> </a> </div> <div class="col-md-3 offset-md-6"> <a href="#"> <button>プロフィール設定</button> </a> </div> </div> </div> </nav> </footer> @endsectionテンプレートの中でも@を付ければPHPのようにforeachを使えます。この際、コントローラーから渡された$estimatesを参照しています。変数の値の展開は{{ }}のように波括弧二つで実現します。
CSSフレームワークにはBootstrapを使用しました。
見積編集ページの作成
商品テーブルの作成
まずは見積テーブルと同様にマイグレーションファイルを作成します。
$ php artisan make:migration create_items_table --create=itemsマイグレーションファイルを記述します。
create_items_table.php<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateItemsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('items', function (Blueprint $table) { $table->increments('id'); $table->integer('estimate_id')->unsigned(); $table->string('name', 100)->nullable(); $table->string('unit', 10)->nullable(); $table->integer('quantity')->nullable(); $table->integer('unit_price')->nullable(); $table->string('other', 100)->nullable(); $table->timestamps(); $table->foreign('estimate_id')->references('id')->on('estimates')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('items'); } }ここでは外部キー制約を設定しています。外部キー制約は他のテーブルとの結びつきを表現するためのカラムに設定します。外部キー制約が設定されたカラムには、好き勝手な値は入れられなくなります。今回の例で言うと、商品テーブルの見積ID列には実際に存在する見積IDの値しか入れることができなくなります。これによりデータの不整合を防ぎます。また、onDelete('cascade')により見積テーブルのデータを削除した場合、商品テーブル内の一致するデータを自動的に削除してくれます。
マイグレーションを実行します。
$ php artisan migrate続けて商品テーブルに対応するモデルクラスを作成します。
$ php artisan make:model ItemItem.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Item extends Model { // }テストデータを挿入するためにシーダーを作成します。
$ php artisan make:seeder ItemsTableSeederItemsTableSeder.php<?php use Carbon\Carbon; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class ItemsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $names = ['ガードレール', 'エムコール', '塩化カルシウム']; $units = ['式', '袋', '袋']; $quantities = [1, 10, 25]; $unit_prices = [150000, 5000, 1000]; foreach (array_map(NULL, $names, $units, $quantities, $unit_prices) as [ $name, $unit, $quantity, $unit_price ]) { DB::table('items')->insert([ 'estimate_id' => 1, 'name' => $name, 'unit' => $unit, 'quantity' => $quantity, 'unit_price' => $unit_price, ]); } } }今回はID=1の見積に対して3つの商品を登録しました。
$ php artisan db:seed --class=ItemsTableSeederルーティングの設定
web.phpRoute::get('/estimates/edit', 'EstimateController@showEditForm')->name('estimates.edit'); Route::post('/estimates/edit', 'EstimateController@edit'); Route::get('/estimates/create', 'EstimateController@create')->name('estimates.create'); Route::post('/estimates/create', 'EstimateController@create');コントローラーの作成
コントローラーを書いていきます。既存の見積もりを編集する場合はそのままshowEditFormへ、新規作成の場合はcreate->showEditFormと推移します。
EstimateController.phpuse App\Item; // ★ 追加 public function showEditForm(Request $request) { $estimate_id = $request->input('estimate'); $estimate = Estimate::find($estimate_id); return view('estimates/edit', [ 'estimate' => $estimate, ]); } public function create() { $estimate = new Estimate(); $estimate->save(); return redirect()->route('estimates.edit', [ 'estimate' => $estimate->id, ]); } public function edit(Request $request) { $estimate_id = $request->input('estimate'); $current_estimate = Estimate::find($estimate_id); $current_estimate->title = $request->title; $current_estimate->location = $request->location; $current_estimate->transaction = $request->transaction; $current_estimate->effectiveness = $request->effectiveness; $current_estimate->customer = $request->customer; $current_estimate->deadline_at = $request->deadline_at; $current_estimate->estimated_at = $request->estimated_at; $current_estimate->save(); return redirect()->route('estimates.edit', [ 'estimate' => $estimate_id ]); }コントローラーメソッドの引数にRequestクラスのインスタンスを受け入れる記述をすることでユーザーの入力値をRequestクラスのインスタンス$requestに詰めて引数として渡してくれます。Requestクラスのインスタンスにはリクエストヘッダや送信元IPなどいろいろな情報が含まれていますが、その中にフォームの入力値も入っています。
$request->title;リクエスト中の入力値は上記のようにプロパティとして取得することができます。
また、クエリパラメータの取得にはRequestクラスのinputメソッドを使用します。inputメソッドの第一引数へ、クエリパラメータのキーを指定します。今回指定するクエリパラメータのキーは'estimate'です。この為、inputメソッドの第一引数には'estimate'を指定します。
次のポイントはデータベースに書き込む処理です。データベースへの書き込みは以下の手順で実装します。
- モデルクラスのインスタンスを作成する。
- インスタンスのプロパティに値を代入する。
- saveメソッドを呼び出す。
これにより、モデルクラスが表すテーブルに対してINSERTが実行されます。
次回
ここまで、見積一覧ページと見積編集ページのコントローラーを作成しました。次回はVue.jsを利用した見積編集ページのテンプレートを作成します。
- 投稿日:2020-12-23T13:56:58+09:00
【Vue.js】Vue.jsをつかむ①
私のアウトプットです。
Vue.jsも人気のフロントエンドフレームワークのはずですが
なぜかReact推しであるブログ記事やYouTubeが多いので
私自身、Vue.jsを学習する上で、理解しておきたいことをまとめました。今回は、敢えてv2.6.11を前提に投稿いたします。
コード全体
index.html<html> <head> <title>Hello Vue</title> <!-- (1) CDNからのVueの読み込み --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script> </head> <body> <!-- (5) Vueインスタンスの有効範囲ここから --> <div id="app"> <!-- (6) マスタッシュ構文 --> <p>Hello {{world}}</p> <p>Counter: {{count}}</p> <!-- (7) v-ifディレクティブ --> <p v-if="count == 5">見えました!</p> <!-- (8) v-modelディレクティブ --> <input v-model="world"><br> <input type="number" v-model="count" /> </div> <script> // (2) Vueインスタンスの作成 new Vue({ el: "#app", // (3) elプロパティ data() { // (4) data()メソッド return { world: "Vue", count: 0 } } }) </script> </body> </html>Vue.jsの読み込み
index.html<!-- (1) CDNからのVueの読み込み --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.js"></script>(1)は、CDNで公開されているVue.jsを読み込んで実行する為のコードです。
プロトタイピングや学習を目的とする場合は、このCDNを使用するのが良さそうです。Vueインスタンスの有効範囲
<body>の中の<script></script>を見ます。index.html<script> // (2) Vueインスタンスの作成 new Vue({ el: "#app", // (3) elプロパティ data() { // (4) data()メソッド return { world: "Vue", count: 0 } } }) </script>(2) は、
new Vue()でVueインスタンスを作っています。
引数に渡しているオブジェクトがVueのコンポーネントになっています。
このオブジェクトを見ていくと、elプロパティ(3)と、data()メソッド(4)があります。elプロパティはDOMに対して指定した値に該当するエレメントを見つけて
対象のエレメントが見つかった時にVue.jsのインスタンスとHTMLをマッピングするセレクタです。data()メソッドはオブジェクトを返すメソッドとして定義しています。
このときのオブジェクトが持つプロパティ((4)では、worldとcount)をVue.jsは監視し続けます。
この監視対象が変化したとき、必要に応じて画面への反映を即座に行ってくれます。DOM上での変数展開
<body>の中の<div id="app"></div>を見ます。index.html<!-- (5)Vueインスタンスの有効範囲ここから --> <div id="app"> <!-- (6) マスタッシュ構文 --> <p>Hello {{world}}</p> <p>Counter: {{count}}</p> <!-- (7) v-ifディレクティブ --> <p v-if="count == 5">見えました!</p> <!-- (8) v-modelディレクティブ --> <input v-model="world"><br> <input type="number" v-model="count" /> </div>これがVueインスタンスが有効な範囲です。
このid="app"で先ほど作ったVueインスタンスとHTMLをマッピングしていきます。
Vueインスタンスのelプロパティ指定と併せて、
別のidを指定したり、classで指定することも可能です。
<p>Hello {{world}}</p>(6)で使われている{{}}という構文はマスタッシュ構文と言います。
見た目が『口ひげ』に似ているからだそうです。
この中ではdata()の中のプロパティにアクセスができ、
data()の中にあるプロパティが変化すると、それに応じた結果が即座に反映されます。v-if
index.html<!-- (7) v-ifディレクティブ --> <p v-if="count == 5">見えました!</p>
v-で始まる属性をVue.jsではディレクティブと言います。
このv-ifは条件付きレンダリングと呼ばれるディレクティブで、
v-ifの後に記述する条件に一致していればDOMにレンダリングされ、
不一致ならDOM上から消えるようになっています。
(言わば、『if文』ですね。)
今回の場合はcountプロパティの値が5の場合に
『見えました!』と表示されることになります。v-model
index.html<!-- (8) v-modelディレクティブ --> <input v-model="world">これは、双方向バインディングと言って、
<input>タグの様な入力を受け付けるタグに対して
data()プロパティを指定できます。
つまり、<input v-model="world">の場合は、
テキストボックスに値を入力すると
その値がdata()のworldに設定されることになります。
これにより、入力フォームのinputイベントを監視して、
data()のプロパティへ随時反応されていくというコードになります。
なお、Vue.jsではv-modelによってフォームと対応付けられているプロパティが認識されている為
識別用のクラスや名前を付与しなくても値の読み取りが可能です。まとめ
(1)CDNからのVueの読み込み
CDNで公開されているVue.jsを読み込んで実行する為のコードです。
(2)Vueインスタンスの作成
new Vue()でVueインスタンスを作っています。(3)elプロパティ
DOMに対して指定した値に該当するエレメントを見つけて
対象のエレメントが見つかった時にVue.jsのインスタンスとHTMLをマッピングするセレクタです。(4)data()メソッド
オブジェクトを返すメソッドです。
(5)Vueインスタンスの有効範囲
id="app"でVueインスタンスとHTMLをマッピングしていきます。(6)マスタッシュ構文
<p>Hello {{world}}</p>で使われている{{}}です。(7)v-ifディレクティブ
v-ifの後に記述する条件に一致していればDOMにレンダリングされ、
不一致ならDOM上から消えるようになっています。(8)v-modelディレクティブ
<input>タグの様な入力を受け付けるタグに対してdata()プロパティを指定できます。
- 投稿日:2020-12-23T13:36:33+09:00
わかりにくいvue.jsのwatch(ウォッチャ)のオプションを使った書き方
概要
vue.jsのwatchにはdeepとimmediateという2つのオプションがあるのですが、書き方がちょっと特殊なのと、マニュアル上で探しにくくいので、癖があります。
毎回ググってしまうので備忘録として残します。※2019/9にブログに書いていた記事からの転記です
watchとは
vueのドキュメントの説明
vueのドキュメントの説明(API)普通は算出プロパティ(computed)でいいんだけど、複雑な処理(重い処理?)の時はこっちの方が良いよ〜との事。
オプション
オプションについてはなぜかちょっと遠いところで、APIの中に記載があります。
[vueのドキュメントの説明]https://jp.vuejs.org/v2/api/index.html#vm-watch
deep
通常、objectのプロパティの変更は、watchが発火しません。
data: () => ({ someObject: {}, )}, watch: { someObject(newVal, oldVal) { console.log(newVal); }, } methods: { onClick: function() { // watchが発火しない! someObject.hoge = 'hoge'; },deepをtrueにする場合はこう
data: () => ({ someObject: {}, )}, watch: { someObject: { deep: true, handler(newVal, oldVal) { console.log(newVal); }, } } methods: { onClick: function() { // watchが発火する someObject.hoge = 'hoge'; },handlerという関数を書かないといけないのが唐突なので、いつも書き方忘れる、、、、
immediate
初期化のタイミングでもwatchが発火するようにしたい場合に使います。
これも、
data: () => ({ someObject: {hoge: 'piyo'}, )}, watch: { someObject: { immediate: true, handler(newVal, oldVal) { console.log(newVal); }, } }という感じで、handler関数にいつもの処理を書いてあげる必要があります。
所感
なんでここだけマニュアルわかりにくいんだろ、、、
- 投稿日:2020-12-23T11:32:31+09:00
Vue.js 3 入門 「Vuex」
はじめに
Vue.js 3 の Vuex について、自分が学んだことを備忘録として記載します。
Vue.js に殆ど触れたことが無い方に少しでも参考になれば幸いです。
誤り等あれば、ご指摘頂けますと大変喜びますVuex とは
Vuexはアプリで利用するデータを、一箇所に集中管理するためのライブラリです。
管理だけではなく、データの操作(更新)方法も標準化することができます。コンポーネント間でのデータ共有がシンプルに実現できるようになりますね。今回のお題
今回は、数字をカウントできる簡単なアプリを作成してみます。
プロジェクトの作成
まずは Vue CLI を用いてプロジェクトを作成します。
Vue CLI についてはこちらの記事を参照してください。プロジェクトを作成するには、作成したいフォルダで以下のコマンドを実行します。
hello-vuexはプロジェクト名です。任意のプロジェクト名を設定してください。cd 任意のフォルダ vue create hello-vuexプリセットの選択
すると、以下のように利用するプリセット(プロジェクト設定)の選択を求められます。
まずは最低限の構成とするので「Manually select features」(手動で選択)を選択します。
versionはご自身のバージョンに読み替えてください。Vue CLI v4.5.9 ? Please pick a preset: Default ([Vue 2] babel, eslint) Default (Vue 3 Preview) ([Vue 3] babel, eslint) > Manually select featuresプロジェクトに組み込むモジュールを選択
プロジェクトに組み込むモジュールを選択します。
ここでBabelとLinterに加えて、Vuexを選択します。
[Space]キーで選択することができ、[Enter]キーで確定となります。
Vuexを選択することによって、Vuexというアプリで利用するデータの、集中管理機能を提供するライブラリが組み込まれます。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: (*) Choose Vue version (*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support ( ) Router >(*) Vuex ( ) CSS Pre-processors (*) Linter / Formatter ( ) Unit Testing ( ) E2E TestingVue.js のバージョンを選択
Vue.js のバージョンを選択します。
本記事では 3.x を選択します。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex, Linter ? Choose a version of Vue.js that you want to start the project with 2.x > 3.x (Preview)Linter の設定を選択
Linterの設定を選択します。
今回は最低限のESLint with error prevention only(エラー防止のみ)を選択します。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Pick a linter / formatter config: (Use arrow keys) > ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier続けて、Lintの実行タイミングの選択を求められます。
Lint on save(保存時)を選択します。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Pick a linter / formatter config: Basic ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Lint on save ( ) Lint and fix on commit設定情報の格納先を選択
BabelとESLintの設定情報を個別の設定ファイルとするか、package.jsonにまとめるかを選択します。
個別の設定ファイルとしたほうが綺麗なのでIn dedicated config filesを選択します。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Pick a linter / formatter config: Basic ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) > In dedicated config files In package.json今回の設定を保存しておくかを選択
今回の設定を保存しておくかを選択します。
今回はあくまでお試しなのでN(保存しない)とします。Vue CLI v4.5.9 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, Vuex, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Pick a linter / formatter config: Basic ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? (y/N) Nプロジェクトの生成開始
ここまでの設定内容を元に、プロジェクトの生成が開始されるので、完了するまで待機します。
正常に完了すると、以下のような文言が表示されます。Vue CLI v4.5.9 Creating project in 任意のフォルダ\hello-vuex. Installing CLI plugins. This might take a while... 途中省略... Running completion hooks... Generating README.md... Successfully created project hello-vuex. Get started with the following commands: $ cd hello-vuex $ npm run serve生成されたフォルダを確認
カレントフォルダに、指定したプロジェクト名のフォルダが生成されています。
アプリの実行
早速実行してみましょう。
上記のプロジェクト生成完了時の文言(Get started with the following commands:)にある通り、以下のコマンドを実行します。
プロジェクトルートに移動して、開発用のサーバーを実行するコマンドです。cd hello-vuex npm run serve以下のような文言が表示されれば、開発用のサーバーが起動できています。
ブラウザを起動しhttp://localhost:8080にアクセスしてください。App running at: 途中省略... Note that the development build is not optimized. To create a production build, run npm run build.動作確認
以下のような画面が表示されれば、プロジェクトの作成は成功です。
開発用サーバーは[Ctrl] + [C]で終了することができます。ストアの定義
まずはストアを定義します。
ストアは主に、データと、データを更新するためのメソッド で構成されます。プロジェクトを作成した時点で、空のストア定義(/src/store/index.js)が用意されているので、こちらを編集していきます。
/src/store/index.jsimport { createStore } from 'vuex' export default createStore({ state: { }, mutations: { }, actions: { }, modules: { } })ストアは
createStoreメソッドで定義することができます。createStoreメソッド
引数
- defs
- ストアを構成する要素(ステート、ミューテーション、etc...)を
要素名:定義の形式で記述します- 複数の要素を定義する際は
,区切りステートの定義
ステートとは、ストアで管理されるデータの本体です。
ステートで管理すべき情報を名前: 初期値の形式で定義します。(複数の場合は,区切り)今回は、カウンターを示す
countを定義します。
/src/store/index.jsimport { createStore } from 'vuex' export default createStore({ state: { count: 0 //追加 }, mutations: { }, actions: { }, modules: { } })ミューテーションの定義
Vuexでは、ステートを専用のメソッド経由で更新します。
ステートの更新フローが限定されるので、コードの見通しが良くなります。このような、ステートを更新するためのメソッドのことを、ミューテーションと呼びます。
今回はステートcountを1増やすincrementメソッドと
countを1減らすdecrementメソッドを定義します。ミューテーションは引数にステート(
state)を受け取るので、実際の各情報にはstate.名前とするとアクセスできます。import { createStore } from 'vuex' export default createStore({ state: { count: 0 }, mutations: { increment(state){ state.count += 1 }, decrement(state){ state.count -= 1 } }, actions: { }, modules: { } })ストアの有効化
なお、ストアは
/src/main.jsで有効化されています。import { createApp } from 'vue' import App from './App.vue' import store from './store' createApp(App).use(store).mount('#app')Vueインスタンスにライブラリを組み込む
useメソッドに、定義したストア(store)を渡すことで有効化しています。ストアにアクセスする
ステートの表示
実際にコンポーネントからストアにアクセスしてみましょう。
まずはストアに定義したステート
countの値を画面に表示してみます。
ステートには、this.$store.state.データの名前でアクセスできます。
/src/App.vueを以下のように修正します。<template> {{count}} </template> <script> export default { name: 'App', computed:{ count(){ return this.$store.state.count }, } } </script>実際の画面を確認すると、初期値に指定した0が表示されています。
ミューテーションの呼び出し
次に、ステート
countの値を増減できるようにしてみます。
ステートを増減させるには、先程定義したミューテーションを呼び出します。
ミューテーションはthis.$store.commit(ミューテーション名)で呼び出すことができます。
/src/App.vueを以下のように修正します。<template> <input type="button" v-on:click="ondecrement" value="-" /> {{count}} <input type="button" v-on:click="onincrement" value="+" /> </template> <script> export default { name: 'App', computed:{ count(){ return this.$store.state.count }, }, methods:{ onincrement(){ this.$store.commit('increment') }, ondecrement(){ this.$store.commit('decrement') } } } </script>実際の画面を確認すると、[+]ボタンと[-]ボタンが増えており、
ステートの値を増減させることができるようになりました。以上となります。
ありがとうございました。
他の機能(ゲッター/アクション/etc...)については別の記事にします。
- 投稿日:2020-12-23T08:30:56+09:00
javascriptの知識ゼロからvue.jsで業務がこなせるようになるまでのロードマップ
プログラミング未経験からベンチャー企業に入社して半年の自分がvue.jsを実務で使えるようになるまでの学習手順について紹介します。vue.jsを初めて使った時ってmethods?computed?何それ?って状態だと思うんですよ。そういう初歩的な疑問をいち早くなくせる人が1人でも増えればいいなーと思ってます。
前提知識
・エディタが使える(vscodeとか)
・HTML+CSSでコーティングがある程度できるレベル目標
vue.jsを使ってこのサイトのような感じで作成できるようにする
①Progate
javascriptを初めて触るならまずここから。
基本的にはカリキュラム通り進めればいいんですが、個人的には3章の関数を学ぼう!までを理解できればいいかなーと。
理由は4章以降はクラスの概念とかファイルの分け方などブラウザに表示させる内容とは関係ないので人によっては学んだ感じがしないんですよね。最初はコードを書いたら色が変わった!動いた!っていう体験をした方がモチベーションは上がるのではないでしょうか?そのため3章までは頑張って理解して4章以降は流し見でいいと思います。②確かな力が身につくJavaScript「超」入門 第2版
javascriptの概念がなんとなくわかった所でもう少し深堀り。
色んな本も見てきましたがカラフル、わかりやすい、サンプルコードつきの3拍子でこの本をおすすめします。
何よりコードを書いたらすぐブラウザに表示されるので勉強してて楽しいです。③HTML + CSS + javascriptを使ったサイト作成
ここで実際にアウトプットしてみましょう。
何作っていいかわからない!という人は模写でも大丈夫です。
GODIVAのマウスを重ねたらメニューが出るぐらいで大丈夫だと思います。
④いちばんやさしい Vue.js 入門教室
javascriptの知識がある程度身についたらvue.jsにシフトしましょう。
vue.jsの本いろいろ読みましたがこの本が一番わかりやすかったです。
なぜならサンプルコード1行1行丁寧に書いてるからです。
他の本だといきなりライフサイクルフックなど難しいワードが出てくるので挫折してしまう可能性があります。⑤vue.jsを使ったサイト作成
Vue CLIを使って実際にサイトを作成。
ニュース一覧などをv-forで繰り返し表現できるとGOOD!
vue.jsの理解を深めるためcssのhoverやfocusといった擬似要素は封印してみましょう。
実際に自分はこのサイトを参考に作成しました。Q&A
サイト作成で挫折した
いい証拠です。足りない知識がわかっているならググるかインプットし直しましょう。それでも挫折しそうなら作成するサイトのレベルを下げましょう。勉強は義務ではないので長続きしなければ意味ありません
他におすすめの教材はないの?
javascript、vue.jsも何冊か本読みましたが2冊目以降はあまり意味なかったなーと思います。
インプットする時間をいち早くアウトプットに回しましょう。さて、続きまして、アルサーガパートナーズ Advent Calendar 2020 24日目の記事は @miumi さんの学習メンタル術です。
- 投稿日:2020-12-23T08:01:02+09:00
プログラミング初学者の現役医師が、firebase使って診断名を管理できるLINEBOTを実装してみた。
皆さん、ご自身の病名(医学的には診断名)を覚えていますか?
僕の本職は医師(開業医)です。
日常診療の課題感から、以下のような、『診断名を保管でき、かつ、必要に応じて引き出せる』アプリを実装しました。
動作は以下です。
診察券番号を伝えると、診断名を返してくれるBot完成!#protoout #デジタルヘルス学会 #プログラミング初学者 #評論家ではなく創造者になろう #linebot pic.twitter.com/IJuusFHLkF
— 北城雅照|ゼロからプログラミング勉強を始めた整形外科医 (@teru3_kitashiro) December 22, 2020医師として、月曜から土曜まで毎週700人以上の患者さまを診察させていただいております。
外来診療を行う中で、「多くの方がご自身の診断名を覚えていらっしゃらない」ことに課題感を持っていました。
診断名がわからないと、次の処置を行っても良いものか、非常に悩む瞬間があるからです。
でも例えば、「好酸球性多発血管炎性肉芽腫症」と診断されても、「飛影邪王炎殺黒龍波」(分かる人いるかな?)みたいに難しく、なかなか覚えられるものではありません。
そこで、上述の『診断名を保管でき、かつ、必要に応じて引き出せる』アプリを実装しました。
システム概要は以下です。
1-1 webappか診察券番号でIDを設定し病名の登録
1-2 webappで診察券番号で病名の確認
2-1 LINEBOTから診察券番号でIDを設定し病名の登録
2-2 LINEBOTで診察券番号で病名の確認
それぞれについて確認します。下準備
必要なした準備は以下の2つ
① VScordのインストール
② firebaseの設定
上記はgoogle先生に譲ります。Webappの実装
今回のwebappはvue.jsで実装します。
webappから病名の登録
addMydg.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>リアルタイムにデータ取得</title> </head> <body> <div id="app"> <p> 診察券番号:<input v-model="cardId" placeholder="00000000"><br> 名前:<input v-model="name" placeholder="名前"><br> 誕生日:<input type="date" v-model="birthday" placeholder="1900/1/1"><br> 性別:<input v-model="gender" placeholder="男or女"><br> 診断名:<input v-model="dgname" placeholder="診断名"><br> 診断日:<input type="date" v-model="dgday" placeholder="1900/1/1"><br> <button v-on:click='post'>送信</button><br> <a href="https://unruffled-curran-e76f78.netlify.app/searchmydg">登録確認のために診断名検索ページに移動する</a> </p> </div> <script src="https://unpkg.com/vue"></script> <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-firestore.js"></script> <script> // firebaseの設定から下記を記入 const firebaseConfig = { apiKey: "", authDomain: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "", measurementId: "" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); const db = firebase.firestore(); let patient = ''; let patientId = ''; const app = new Vue({ el: '#app', data: { allData: [], cardId:'', name: '', birthday: '', gender: '', dgname: '', dgday: '', }, methods: { //データ追加 post: async function () { patient = this.cardId const res = await db.collection("medicalRecord1").doc(patient).set({ name: this.name, birthday: this.birthday, gender: this.gender, diagnosis: {dgday:this.dgday, dgname:this.dgname}, cardId:this.cardId }); console.log(patient); }, } }) </script> </body> </html>webappで病名の確認
search.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>診断名検索</title> </head> <body> <div id="app"> <h1>診断名検索</h1> <input v-model:value="cardID" placeholder="診察券番号"><br> 診察券番号:{{ cardID }} <button v-on:click="searchDg">検索</button><br> <p> 氏名:{{ name }}<br> 診断名:{{ diagnosis }}<br> 診断日:{{ diagnosisday }} </p> <button v-on:click="clearAll">全てをクリア</button><br> <a href="https://mystifying-tesla-bb64e7.netlify.app/addmydg">診断名登録ページに移動する</a> </div> <script src="https://unpkg.com/vue"></script> <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-firestore.js"></script> <script> // firebaseの設定から下記を記入 const firebaseConfig = { apiKey: "", authDomain: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "", measurementId: "" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); const db = firebase.firestore(); const app = new Vue({ el: '#app', // Vueが管理する一番外側のDOM要素 data: { // Vue内部で使いたい変数は全てこの中に定義する cardID:'', name:'', diagnosis: '', diagnosisday:'', }, methods: { // 関数はここに記入 searchDg: async function() { console.log('次の診察券番号の患者さまが検索されました:', this.cardID); const patientData = await db.collection("medicalRecord1").doc(this.cardID).get(); console.log(patientData.date); //データの格納 const pData = patientData.data();//繰り返し各箇所なので一回定数に this.name = pData.name; this.diagnosis = pData.diagnosis.dgname; this.diagnosisday = pData.diagnosis.dgday; }, clearAll: function() { this.cardID = ''; this.name = ''; this.diagnosis = ''; this.diagnosisday = ''; console.log('全てのToDoが消去されました'); }, }, }); </script> </body> </html>LINEBotから病名の登録
リッチメニューを設定し、病名登録のwebappがLINEの中で立ち上がるように設定しました。
同様に、病名確認のwebappも立ち上がるように設定しました。
診断名の新規登録はリッチメニューから、登録用のサイトに飛ぶ設定になってます。#protoout #デジタルヘルス学会 #プログラミング初学者 #評論家ではなく創造者になろう pic.twitter.com/iYpa1BMKI4
— 北城雅照|ゼロからプログラミング勉強を始めた整形外科医 (@teru3_kitashiro) December 22, 2020LINEBotで病名の確認
診断名を確認する場合、“診断名確認”とLINEBotに送ると、“診察券番号を教えてください”と返信させ、診察券番号を送ると名前・診断名・診断日を返すように設定しました。まずは、必要なパッケージの導入
以下のコードを順番にターミナルに入力ターミナルコマンド$ npm init -yターミナルコマンド$ npm i expressターミナルコマンド$ npm i @line/bot-sdkターミナルコマンド$ npm i firebase今回作成したコードは以下の通り。
searchMydg.js'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const firebase = require("firebase/app"); require("firebase/firestore"); // firebaseの設定から下記を記入 const firebaseConfig = { apiKey: "", authDomain: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "", measurementId: "" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); const db = firebase.firestore(); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIの設定から記入 const config = { channelSecret: '', channelAccessToken: '' }; // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } //診断名検索というテキストを受け取ったら診察券番号を確認するリプライを出す if (event.message.text == '診断名確認') { return client.replyMessage(event.replyToken, { type: 'text', text: '診察番号を入力してください' }); } //cardIDの中に審査券番号が格納 const cardID = event.message.text; console.log(cardID); //診察券番号からfirebase上の診断名を検索 const patientData = await db.collection('medicalRecord1').doc(cardID).get(); console.log(patientData.data()); const pData = patientData.data(); if (pData != undefined) { const name = pData.name; const diagnosis = pData.diagnosis.dgname; const diagnosisDay = pData.diagnosis.dgday; let msg = ''; msg = 'お名前:'+ name + '\n' +'診断名:' + diagnosis + '\n' +'診断日:' + diagnosisDay console.log(msg); const message = { type: 'text', text: msg }; client.replyMessage(event.replyToken, message); }else{ const message = { type: 'text', text: '再度診察券番号を記入してください。' }; client.replyMessage(event.replyToken, message); }; } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP GETによって '/' のパスにアクセスがあったときに 'Hello LINE BOT! (HTTP GET)' と返事します // これはMessaging APIとは関係のない確認用のものです app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)')); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // Webhookの中身を確認用にターミナルに表示します console.log(req.body.events); // 空っぽの場合、検証ボタンをクリックしたときに飛んできた"接続確認"用 // 削除しても問題ありません if (req.body.events.length == 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);今後の課題
診断名を一つではないので、診断名をリスト化し、LINEBotに返せる設定を実装したいと思いました。
もう少し深めて行く予定です。その他の記事
近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。
誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。
- 投稿日:2020-12-23T02:49:35+09:00
V-forとV-showで特定の要素だけclassをつける
やりたいこと
DBからひっぱってきたデータをvueのコンポーネントの中のdataにセットし、それをv-forで表示させているとします。
DBからひっぱってきたデータのいずれかに真偽値を設定するなどしておけばv-forの中で動きの変化はつけることができますが、
「そのコンポーネントの中だけのdataを追加し、v-forの中の特定のコンテンツだけclassをつける」などの変化をつけたい場合にやる方法。記述内容
//描写部分 <template> <div> <div v-for="task in tasks" :key="task.id"> <div class="task__title">{{task.title}}</div> //例えばこのtask.bodyだけ、条件に応じてis-activeclassをつけたい場合 <div class="task__body" v-bind:class="{'is-active':active === task}" >{{task.body}} </div> <button v-on:click="addActive(task)">bodyにclassをつける</button> </div> </template> <script> export default { data(){ return{ tasks:[ ],//この中にdbからひっぱってきたデータが入っている active:null } }, methods:{ addActive(task){ if(this.active === task){ this.active = null; }else{ this.active = task; } } } } </script>こんな感じで、v-forの中の値(task)を引数として渡すことで、そのactiveをtaskにすることができる。
これにより特定のtaskだけにclassを適用できる。
- 投稿日:2020-12-23T00:44:18+09:00
Vuetify+TypeScriptでちょっとリッチなmarkdownを
前書き
この記事は JSL (日本システム技研) Advent Calendar 2020 - Qiita 22日目の記事です。
Vueでマークダウン書きたいよ、という要望に応えるために色々やってみた備忘録になります。
なんか、あまりこの組み合わせのやってみた記事を見かけないけどなんで・・・???![]()
環境
MacOS: Mojave v10.14.5Node: v10.19.0TypeScript: v3.9.7Vue.js: v2.6.12Vuetify: v2.3.21楽をするために我々はnpmの奥地へと向かった...
vuetify-markdown-editor
なんかそれっぽいのが見つかりました。 vuetify-markdown-editor ですね。覗いてみましょうか・・・
おおぉ・・・、もう今回の要件ほとんど満たしているじゃありませんか。
Vuetifyコンポーネントをベースにしているし、リッチですし、TypeScriptにも対応しているしで至れりつくせりです。
もうこれでいいじゃん...と思いましたが、今後でてくるであろう要望をどこまで吸収できるのかが不安でした(使用例があんまり見つからない)とはいえ、サクッと用意したいならこれを使わない理由はないのではないでしょうか?
vuetify-markdown-editor以外にVuetifyをベースにしたものは見つかりませんでした。
今後の自由度考えて独自に実装しちゃおう、ということで本編です。車輪の再開発?本編
markdown-itを導入してみる
色々調べてみて、 markdown-it が一番一般的っぽかったのでこれにしました。マークダウン形式で書かれたテキストをパースしてhtmlにしてくれるパッケージです。
導入に当たっては、VuejsでMarkdownを使うときの最強な組み合わせ を参考にさせていただきました。
markdown-itにはプラグインがたくさんあって、結構カスタムできるみたいです。で、以下のプラグインを導入することにしました。(タイトルは「リッチな」とうたっていますし豪華に行きましょう)
highlight.js: コードのハイライトしてくれるやつ(重要)
markdown-it-emoji: 名前の通り出すためのやつ
markdown-it-imsize: 画像サイズを調整できるようにするやつ
markdown-it-ins: 文字に下線ひけるやつ
markdown-it-sanitizer: XSS対策でサニタイズできるやつ
markdown-it-task-checkbox: チェックボックス出せるようにするやつ基本は参考記事の通りで問題ありませんが、ここでTypeScript特有の問題が発生します。
そう、型定義です。
@types/hogeパッケージが配布されていればいいのですが、以下のパッケージにはありませんでした。
declare module hogeでanyにしてもいいのですが、一応ちゃんとやってみることにしました。(えらいぞ自分)まぁやることは簡単で、ちゃんと
.d.tsファイルに型定義を書いてあげればよいです。(たぶん)markdown-it-plugins.d.tsdeclare module "markdown-it-task-checkbox" { import { PluginSimple, PluginWithOptions } from "markdown-it"; declare const checkbox: PluginSimple | PluginWithOptions; export = checkbox; }この要領で他のやつの分も追記していきます。
エディタもちゃんと分かってくれました(型を)
とりあえず試してみる
参考資料を元に実装してみました。動かしてみましょう。
コードブロックすらまともに出ない・・・だと????
![]()
悪さをしているのはVuetifyのCSS、<code>タグになんかあたってました。
ならばこちらもCSS書いて上書きしてやるまでです。Markdown.vue// 省略 <style lang="scss" scoped> // 参考記事のCSSインポートはここでやることに @import "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.css"; @import "../../node_modules/highlight.js/styles/monokai.css"; .v-application .md ::v-deep { code { font-weight: initial; background-color: #2f2f2f; color: rgba(255, 255, 255, 0.9); font-size: 95%; display: inline-block; padding: 1px 0.5em 1px 0.5em; margin: 2px; margin-bottom: 14px; } pre code { display: block; padding: 0.5em 0.8em 0.5em 0.8em; } code:before, code:after { content: initial; } p { margin-bottom: 8px; } table { margin: 16px 0; color: #303030; width: auto; display: block; overflow-x: auto; border-collapse: collapse; th, td { padding: 10px; border: 1px solid #dbdbdb; } th { background-color: #f0f0f0; border-bottom: solid 2px #bfbfbf; } } }※
::v-deepは子コンポーネントにstyleを指定するためのセレクタですちょっと
scss書くの楽しくて他もいじっちゃいましたが許してください
これでどうでしょうか。やりました。
調教完了ですmarkdown-it-containerをVuetify標準にしよう
何もしないとこんなです
あれ、リッチとは????
![]()
その時思いました、Vuetifyのクラスをつけてあげれば勝手にVuetifyのデザインになるのでは、と。
早速やってみます。プラグインのオプションで変換処理を上書きして実現しようという魂胆です。Vuetifyの
v-alartコンポーネントのクラスを拝借して・・・markdownRender.ts// 省略 type AlertType = "success" | "info" | "warning" | "error"; const createContainerOptions = (a: AlertType) => { const pattern = new RegExp(`^${a}$`); return [ a, { render: (tokens: Token[], idx: number) => { const m = tokens[idx].info.trim().match(pattern); if (m && tokens[idx].nesting === 1) { return `<div class="v-alert v-alert--dense v-alert--text ${a}--text">\n`; } return `</div>\n`; } } ]; }; const md = markdownIt({ highlight: function(code, lang) { return hljs.highlightAuto(code, [lang]).value; }, html: true, linkify: true, breaks: true, typographer: true }) .use(container, ...createContainerOptions("success")) .use(container, ...createContainerOptions("info")) .use(container, ...createContainerOptions("warning")) .use(container, ...createContainerOptions("error")) // 省略こんなんでどうよ・・・
完☆璧
完成品
こんな感じで、ちょっとリッチな表示ができているのではないでしょうか。
styleとプラグインで自由に拡張できるのは楽しいですね。
Vuetifyくんに邪魔されることもありますが、仲良くできればいくらでも活かすことができそうです!夢が膨らみますあとがき
エディタのツールバーとかはハリボテです・・・ごめんなさい・・・
















