20200531のvue.jsに関する記事は9件です。

かんたんLaravel + Vue.jsでVueUIを使用してログイン、登録の実装

LaravelにはwebpackのようなLaravel-Mixがあり、それを使うことによりLaravel上で
Vue.jsが実装できます。
今回は、いくつかのコマンドを打つことでかんたんにVueUIを使用してログイン、登録の実装します。

ターミナルにコマンドを打ち込んでいきます。

  # laravelプロジェクト作成
 laravel new 「プロジェクト名」
 #作成したプロジェクトに移動
 cd 「プロジェクト名」

npmは、Node Package Managerの略でNode.jsのパッケージ(Package)を管理する(Manager)ツールです。
インストール方法は、以下のgithubを参考してください。
https://github.com/npm/cli
ダウンロードは、以下のリンク
https://nodejs.org/en/download/

#npmをインストール
npm install
#一度コンパイルをします。
npm run dev

問題がなければVueのログイン・php登録UIをインストールします。
2回コマンドを入力しなければなりません。

#1 インストールに時間がかかります。
composer require laravel/ui
#2 1が終了後
php artisan ui  vue --auth

最後に

npm install && npm run dev

サーバーを立ち上げて確認します。

php artisan serve

完成!!!

スクリーンショット 2020-05-31 22.23.46.png
スクリーンショット 2020-05-31 22.40.41.png
スクリーンショット 2020-05-31 22.40.52.png

以下のパスにExampleComponent.vueもインストールされてます。
resources/js/components/ExampleComponent.vue
これの表示の仕方は、次回します。

bootsrapも使えるように設定されており、bootsrapを使うためにJquery,Popper.jsも入れられています。
package.jsonを確認するとわかります。
スクリーンショット 2020-05-31 22.37.36.png

PHPをすぐに始めることができるMAMP(Mac), XAMPP(Windows)があります。

MAMP
https://www.mamp.info/en/mac/
XAMPP
https://www.mamp.info/en/mac/

laravelの環境構築は、すごく大変でした。。。
MacOS違いだけでもやり方が違ったり。

僕は、Udemyを利用して環境構築をしました。
【2日でできる】はじめての PHP 7 x Laravel 6 入門
https://www.udemy.com/course/php7study/

少しでも参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】イメージをhoverすると扉が閉まるように背景が現れて文字が表示されるCSSアニメーション実装

スクリーンショット 2020-05-30 20.47.03.png

Vueバージョン確認

npm list vue

まずは上記コマンドでバージョンの確認

twinzlabo@0.1.0 /Users/twinzlabo

── vue@2.6.11

イメージをhoverするとカッコよく文字が表示されるCSSアニメーション実装

すでに上の方で確認してもらったかと思いますが、

特に変哲もない画像にスタイル修正を行うことで

画像をhoverするとスーッと白い背景+descriptionが現れるクールなアニメーション実装をしていきましょう
スクリーンショット 2020-05-30 20.47.16.png

デフォルトの上の画像をhoverしたら下の画像のように背景と文章が現れるアニメーションをカスタマイズしていきます
スクリーンショット 2020-05-30 20.47.03.png

この感じなかなかクールですよね

では早速コードをコピペしていきましょう

<template>
  <div class="container">
    <div class="bg-pic">
      <div class="pic" style="background-image : url('https://cdn.pixabay.com/photo/2017/01/17/23/05/valetta-1988455_1280.jpg');">
        <div class="screen-left"></div>
        <div class="screen-right"></div>
        <div class="fonts">
          <h1>Malta's capture</h1>
          <p>this is a photo in malta <br><br><br>Have A Good Time</p>
        </div>
      </div>
    </div>
  </div>
</template>
<style>
body {
  background-color: #E43;
}

.container {
  width  : 960px;
  margin : 0 auto;
}

.container:after{
  clear   : both;
  display : table;
  content : '';
}

.bg-pic {
  width  : 640px;
  height : 500px;
  margin : 20px;
  background-color: white;
  float  : left;
  cursor : pointer;
  box-shadow : 3px 3px 5px 0px rgba(0,0,0,0.5);
}

.pic {
  width  : 640px;
  height : 500px;
  position: relative;
  overflow: hidden;
  background-color: #102B46;
}

.fonts {
  background-color : #ffffff;
  width            : 640px;
  height           : 500px;
  padding          : 10px;
  top : 0;
  left: 0;
  font-family : georgia;
  color       : #888888;
  opacity : 0;
  transition : opacity .8s;
}

.fonts h1 {
  margin-top: 100px;
  margin-bottom : 40px;
}

.fonts p {
  font-size : 14px;
  font-style: italic;
  text-align: center;
  line-height : 20px;
}

.pic:hover .fonts {
  opacity : 1;
  transition : opacity .2s .3s;
}

.pic div {
  position : absolute;
}


/* screen open and close */

.screen-right , .screen-left{
  width : 50%;
  height: inherit;
  background-color : #ffffff;
  top : 0;
  transition : all .3s;
}

.screen-right {
  left : 100%;
}

.screen-left {
  right : 100%;
}

.pic:hover .screen-right {
  transition : all .3s;
  left : 50%;
}

.pic:hover .screen-left {
  transition : all .3s;
  right : 50%;
}
</style>

いかがでしたでしょうか?

画像をhoverするとアニメーションが実行されましたか?

下の記事では別の応用的な画像のデザイン方法を掲載しているので是非挑戦してみてください

以上です

より応用的でお洒落な画像一覧画面を作りたい方にはこちらの記事がおすすめです
【Vue/画像一覧をコピペだけ】イメージをhoverすると背景が閉じるように変化し文字が現れるアニメーション
スクリーンショット 2020-05-31 0.07.40.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】ライフサイクルフック、イベント/キー修飾子のまとめ(コンポーネントもあるよ)

Vueまとめパート2

こちらの記事は、Adnan Babakan 氏によりDev.to上で公開された『 Vue cheat sheet 2 』の邦訳版です(原著者から許可を得た上での公開です)

原著をベースに説明の足りない部分は適宜、追記していく予定です。
(追記・改変の許可は得ています。)


Cover image for Vue cheat sheet 2

DEV.toコミュニティのみなさん、こんにちは!

これは『Vueまとめ』シリーズのパート2です。

一部の説明とコードサンプルは、Vue.js公式ウェブサイトから引用しています。

チートシート

ライフサイクルフック

ライフサイクルフック 実行されるタイミング
beforeCreate Vueインスタンスが初期化された後(Vueインスタンスが作成される前)
created Vueインスタンスが作成された後
beforeMount Vueインスタンスがマウントされる前
mounted Vueインスタンスがマウントされた後
beforeUpdate dataプロパティの値に変更があってDOMが更新される前
updated dataプロパティの値に変更があってDOMが更新された後
beforeDestroy Vueインスタンスが破棄される前
destroyed Vueインスタンスが破棄された後

ライフサイクルフック(Lifecycle Hook)

ライフサイクルフックとは、特定のイベント/タイミングで実行されるVueコンポーネント内の関数のこと。

beforeCreate

beforeCreate()はインスタンスが初期化された直後、データ(data)監視とイベント/ウォッチャ(watch)のセットアップの前に同期的に呼び出される。

const app = new Vue({
    beforeCreate () {
        console.log('Vueインスタンスが初期化されました!')
    }
})

created

created()はインスタンスの作成後に同期的に呼び出される。この段階では、インスタンスはオプション群の処理を完了している。つまり、データ(data)監視、算出プロパティ(computed)、メソッド(methods)、ウォッチャ(watch)、イベントコールバックのセットアップが完了している状態。ただし、マウントフェーズはまだ開始されておらず、$elプロパティはまだ利用できない。

const app = new Vue({
    created () {
        console.log('Vueインスタンスが作成されました!')
    }
})

beforeMount

マウントが始まる直前に呼び出される(レンダー関数が初めて呼び出される直前のタイミング)。1

const app = new Vue({
    beforeMount () {
        console.log('Vueインスタンスがマウントされようとしています!')
    }
})

mounted

インスタンスがマウントされた後に呼び出され、elは新しく作成されたvm.$elに置き換えられる。ルートインスタンスがドキュメント内要素にマウントされている場合、vm.$elmounted()が呼び出されるとドキュメント内要素になる。2

const app = new Vue({
    mounted () {
        console.log('Vueインスタンスがマウントされました!')
    }
})

beforeUpdate

DOMにパッチが適用される前に、データ(data)が変更されたときに呼び出される。これは、更新前に既存のDOMにアクセスするのに適したタイミングである。(たとえば、手動で追加されたイベントリスナーを削除する場合など)。3

const app = new Vue({
    beforeUpdate() {
        console.log('DOMが変更されようとしています!')
    }
})

updated

updated()はデータ変更によって仮想DOMが再レンダリングされてパッチが適用された後に呼び出される。

このフックが呼び出されると、コンポーネントのDOMが更新されるため、ここでDOMに依存する操作を実行できる。ただし、ほとんどの場合、このフック内で状態を変更すべきではない(無限更新ループになるため)。状態の変化に対応するには、代わりに算出プロパティ(computed)かウォッチャ(watch)を使うのが普通は良いとされる。4

const app = new Vue({
    updated() {
         console.log('DOMが更新されました!')
    }
})

beforeDestroy

Vueインスタンスが破棄される直前に呼び出される。この段階では、インスタンスはまだ完全に機能している。5

const app = new Vue({
    beforeDestroy() {
        console.log('Vueインスタンスが破棄されようとしています!')
    }
})

destroyed

destroyed()はVueインスタンスが破棄された後に呼び出される。このフックが呼び出されると、Vueインスタンスのすべてのディレクティブがバインド解除され、すべてのイベントリスナーが削除され、すべての子Vueインスタンスも破棄される。6

const app = new Vue({
    destroyed() {
        console.log('Vueインスタンスが破棄されました!')
    }
})

Events イベント

イベントは、HTMLにおける一つの要素おいて特定のアクションが実行されたときに呼び出される。

このシリーズの前の記事では、イベントについて基本的なことを説明したが、ここではもう少し詳しく説明する。

ちなみに@v-onの省略形なのでお忘れなく。

利用可能なイベント

v-onを使うと、すべてのJavaScriptイベントにアクセスできる。

<!-- ボタンに対して次のアクションが行われるとアラートが出る -->
<button @click="() => alert('Hello')">Do it</button>
<button @mouseover="() => alert('Hello')">Do it</button>
<button @mouseout="() => alert('Hello')">Do it</button>
<button @contextmenu="() => alert('Hello')">Do it</button>

フォームでsubmitイベントを発生させることもできる。

<form @submit="() => alert('This form is submitted')">
  <input type="text" />
</form>

イベント修飾子

イベント修飾子は、イベントの一部の動作を変更したり、イベントをより詳細に制御したりするために使用される。

イベント名の後の.に続けて修飾子を書く。

/* イベント修飾子の使い方 */
v-on:<イベント名>.<イベント修飾子>
// または
@<イベント名>.<イベント修飾子>
// 修飾子をチェーンして書くこともできる(書いた順に実行される)
@<イベント名>.<イベント修飾子1>.<イベント修飾子2>

.stop

<!-- クリックイベントの伝搬が止まる -->
<a v-on:click.stop="doThis"></a>

.prevent

<!-- submit イベントによってページがリロードされない -->
<form v-on:submit.prevent="onSubmit"></form>

.capture

<!-- use capture mode when adding the event listener -->
<!-- i.e. an event targeting an inner element is handled here before being handled by that element -->
<div v-on:click.capture="doThis">...</div>

.once

.onceを使うと指定したクリックイベントは最大1回しかトリガーされない。

<!-- クリックイベントは1回だけ実行される -->
<a v-on:click.once="doThis"></a>

.self

<!-- event.target が要素自身のときだけ、ハンドラが呼び出される -->
<!-- 言い換えると子要素のときは呼び出されない -->
<div v-on:click.self="doThat">...</div>

.passive

<!-- `onScroll` が `event.preventDefault()` を含んでいたとしても -->
<!-- スクロールイベントのデフォルトの挙動(つまりスクロール)は -->
<!-- イベントの完了を待つことなくただちに発生するようになる -->
<div v-on:scroll.passive="onScroll">...</div>

キー修飾子/システム修飾子キー

Vueには、キーボードイベントを利用するときに、特定のキーをリスンするための修飾子が用意されている。

これらの修飾子は、keydown(キーを押す)またはkeyup(キーを押してから離す)などのいづれのキーイベントと一緒に使うことができる。

.enter

「enter」キーに反応をリスンする

<!-- Enterキーが押されたときにアラートする -->
<input @keydown.enter="() => alert('enterキーが押されました!')">

.tab

「tab」キーに反応をリスンする

<!-- Tabキーが押されたときにアラートする  -->
<input @keydown.tab="() => alert('tabキーが押されました!')">

.delete

「delete」キーと「backspace」キーの反応をリスンする。

<!-- deleteキーorbackspaceが押されたときにアラートする -->
<input @keydown.delete="() => alert('deleteキーが押されました!')">

.esc

「esc」キーの反応をリスンする。

<!-- escキーが押されたときにアラートする -->
<input @keydown.esc="() => alert('espキーが押されました!')">

.space

「space」キーの反応をリスンする。

<!-- spaceキーが押されたときにアラートする -->
<input @keydown.space="() => alert('spaceキーが押されました!')">

.up

「↑カーソル」キーの反応をリスンする。

<!-- ↑カーソルキーが押されたときにアラートする -->
<input @keydown.up="() => alert('↑カーソルキーが押されました!')">

.down

「↓カーソル」キーの反応をリスンする。

<!-- ↓カーソルキーが押されたときにアラートする -->
<input @keydown.down="() => alert('↓カーソルキーが押されました!')">

.right

「→カーソル」キーの反応をリスンする。

<!-- →カーソルキーが押されたときにアラートする -->
<input @keydown.right="() => alert('→カーソルキーが押されました!')">

.left

「←カーソル」キーの反応をリスンする。

<!-- ←カーソルキーが押されたときにアラートする -->
<input @keydown.left="() => alert('←カーソルキーが押されました!')">

.home

「home」キーの反応をリスンする。

<!-- homeキーが押されたときにアラートする -->
<input @keydown.home="() => alert('homeキーが押されました!')">

.end

「end」キーの反応をリスンする。

<!-- endキーが押されたときにアラートする -->
<input @keydown.end="() => alert('endキーが押されました!')">

.ctrl

「ctrl」キーの反応をリスンする。

<!-- ctrlキーが押されたときにアラートする -->
<input @keydown.ctrl="() => alert('ctrlキーが押されました!')">

.alt

「alt」キーの反応をリスンする。

<!-- altキーが押されたときにアラートする -->
<input @keydown.alt="() => alert('altキーが押されました!')">

.shift

「shift」キーの反応をリスンする。

<!-- shiftキーが押されたときにアラートする -->
<input @keydown.shift="() => alert('shiftキーが押されました!')">

.meta

「meta」キーの反応をリスンする。
(Mac:コマンドキー(⌘)、Windows:ウィンドウキー(⊞))

<!-- metaキーが押されたときにアラートする -->
<input @keydown.meta="() => alert('metaキーが押されました!')">

Custom key code カスタムキーコード

Vueが必要なキー修飾子を提供しない場合は、次のようにそのキーコードを使うことができる。

<!-- 
キーコード「49」が押されたときにアラートする
(キーコード「49」は、キーボード上部の「1」。テンキーの「1」ではない)
-->
<input @keydown.49="() => alert('キーコード「49」キーが押されました!')">

キー修飾子の組み合わせ

次のように、複合キーに対応するためにキー修飾子をチェーンできる。

<input @keydown.ctrl.shift="() => alert('ctrlキーとshiftキーが同時に押されました!')">

.exact

exactを使うと、必要なボタンだけを捕捉できる。他のボタンと一緒に使用した場合は捕捉されない。

exactを使用しない場合、次のコードでShiftキーを押しながらCtrlキーを押すと、正常に応答する。

<!-- Shiftキー+Ctrlキーでもアラートが出る -->
<input @keydown.ctrl="() => alert('やあ!')">

ただし、以下のコードを使用すると、ctrlだけを押さないと正常に応答しない。

<!-- ctrlキーだけを押す必要がある -->
<input @keydown.exact.ctrl="() => alert('やあ!')">

コンポーネント(Component)

コンポーネントは再利用可能なVueインスタンスであり、コードを簡素化・分割するために使用される。

次がVueコンポーネントの一例。

Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

できる限り、テンプレートとそれに使用されるデータが埋め込まれていて、他のVueコンポーネント内で使用できるようにする。

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

これは、互いに何の関係もない3つの異なるボタンをレンダリングします。それぞれが個別に機能し、それぞれが独自のcountデータを持っている。(コンポーネントを定義する場合、各インスタンスが独自のデータを持たずデータが共有される場合を除いて、dataはオブジェクトを返す関数である必要がある。)

単一ファイルコンポーネント(Single File Component)

単一ファイルコンポーネント(またはSFC)は、1つのファイルとしてまとまっている1つのコンポーネントのこと(すごい!)。

次のようによりまとまった構造になっている。

<template>
    <div>

    </div>
</template>

<script>
    export default {

    }
</script>

<style scoped>

</style>

コンポーネントの登録

コンポーネントを登録することにより、登録したコンポーネントをテンプレートで使用できる。

グローバルにコンポーネントを登録する

component()を実行することにより、コンポーネントはすべてのVueインスタンスで利用可能になる(Vueインスタンスが複数ある場合):

import Vue from 'vue'

Vue.component('my-component', require('/path/to/your/component'))

インスタンスのスコープを制限して登録する

次のようにすることで、コンポーネントは指定されたVueインスタンスでのみ使用可能になる。

import Vue from 'vue'

const myComponent = require('/path/to/your/component')

const app = new Vue({
    components: {
        myComponent
    }
})

遅延読み込みでコンポーネントを登録する

この方法は、コンポーネントをメインエントリファイルにバンドルしないため、非常に優れている。このため、Webサイトの読み込みが速くなり、必要なコンポーネントのみが必須になる。

import Vue from 'vue'

const myComponent = () => import('./components/myComponent ')

const app = new Vue({
    components: {
        myComponent 
    }
})

ついでに付け加えると、この登録方法では0などの番号で名前が付けられたコンポーネントが抽出される。webpackを使用している場合は、次のようにコンポーネントのファイル名を変更するためにマジックコメントを使用できる。

import Vue from 'vue'

const myComponent = () => import(/* webpackChunkName: "myComponent" */ './components/myComponent ')

let app = new Vue({
    components: {
        myComponent 
    }
})

props

コンポーネントは、HTMLタグの属性のように機能するpropsを持つことができます。

次のようなコンポーネントがあるとする。

Vue.component('blog-post', {
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})

次のように使うことができる。

<blog-post post-title="hello!"></blog-post>

注:propsを定義する場合は、キャメルケースを使用して定義する方が適切ですが、使用する場合は、ケバブケースとして使用します。逆にVueでケバブケースで定義されたpropsをキャメルケースで使用することは可能だが、IDEがうまく認識できない可能性があるので注意。

単一ファイルコンポーネントには、propsを含めることもできる。

<template>
    <div>

    </div>
</template>

<script>
    export default {
           props: ['myProp'];
    };
</script>

<style scoped>

</style>

slots

slotsは、コンポーネント内に他の要素またはコンポーネントを埋め込むために使用される。

シンプルなslot

単純なslotは名前のないslotであり、次のようにテンプレートで使用される。

<a :href="url" class="strange-class"><slot></slot></a>

コンポーネント名がnavigation-linkurlがpropと想定して上記のテンプレートを定義した後、次のように使うことができる。

<navigation-link url="/my-profile">Your profile</navigation-link>

見てのとおりコンポーネントの内部にテキストがあり、それは<slot></slot>の代わりに挿入される。

名前付きslots

名前付きslotsは、名前なしスロットとは少し異なる。

次のbase-layoutというコンポーネントを定義する。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

これを次のように使うことができる。

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

v-slot:slot-nameのついたテンプレートは、コンポーネントで定義されたそれぞれのスロットに配置され、その他は名前のない<slot></slot>に配置される。

次のように名前のないslotには、実際にはdefaultという名前でアクセスできる。

<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

スコープ付きslots

slotはその親propにアクセス可能だが、避けたい場合は次のようにslot propを定義できる。

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

そして使用するときは次のようにする。

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

このスロットはそのコンポーネントで利用可能なuserを使用し、親コンポーネントで利用可能なuserは使用しない。


  1. beforeMount()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  2. mounted()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  3. サーバーサイドでは最初1回のレンダリングのみ実行されるので、beforeUpdate()はサーバーサイドレンダリング(SSR)では呼び出されない。 

  4. updated()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

  5. beforeDestroy()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

  6. destroyed()は、サーバーサイドレンダリング(SSR)では呼び出されない。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【vue.js】Maximum call stack size exceededがでた

コンポーネントを無限ループで呼ぶなどをしているとこのエラーがでるようです。

ただこういったケースでなくてもエラーが出ることがありました。
僕の場合は結構な量のイラストをインラインのSVGとしてvueのコンポーネントに貼り付けて呼び出したらなりました。

1つのvueファイルはmaxのサイズがあるみたいですね!
SVGをなんとか軽量化するか、更にファイル分けるで対策しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン駆動設計+Nuxt/Vueでリアルタイムバリデーション[発展編]

はじめに

以前の記事、「ドメイン駆動設計 with Vue/Nuxt(Composition API)でリアルタイム・バリデーション」の発展型を作ってみました。

当初VeeValidate3が発表され少し勉強しようと使っていたのですが、その2系とは異なる破壊的変更に当初は当然のごとく戸惑いました。いざ自分で似たようなものを作ってみるとinvalidがv-slotで呼ばれている理由など新しい発見も多かったです。
そこで前回の記事を元にして、もう少しVeeValidateに似せたドメイン駆動設計対応のリアルタイムバリデーションを作ってみます。

要件としては

  • ドメインオブジェクトに対する入力情報の可否ロジックの責務はドメインオブジェクトが担う(ここを分離するのが不満だった)
  • リアルタイムバリデーションする
    • エラーは即座に、そのエラーが出ているフォーム付近に表示する
  • 実装があんまり難しくならないようにする
  • アトミックデザインにも対応できるようにする
    • 今回は深く取り組んでいないですが修正すれば可能

コード

GitHubにあげています。
devinoue/realtime-validation-ddd-advanced

ドメイン層の実装

以下は一例で、しかも前回と同じものです。
validationメソッドは必要です。

export default class Name {
  constructor(private _name: string) {
    Name.validation(this._name)
  }

  static validation(name: string): never | void {
    if (name === '') {
      throw new Error('名前を入力してください')
    }
    if (typeof name !== 'string') {
      throw new TypeError('名前は文字列にしてください')
    }
    if (name.length > 8) {
      throw new Error('名前は8文字以内にしてください')
    }
  }
}

子コンポーネントフォーム

使いまわしできるような子コンポーネントinputフォームです。
このフォーム内で設定されるのは、idtypeplaceholderなどHTML寄りの要素です。maxlengthを入れなかったのは、それはドメイン側で操作できたほうが良いかなという理由。

エラーの位置やラベルの位置などはここをいじる、またはそもそもコンポーネントごとに分離するというアトミックデザイン的なやり方もありかもしれません。

<template>
  <div>
    <label :for="labelId">{{ labelName }}</label>
    <input
      :id="labelId"
      v-model="value"
      type="inputType"
      :placeholder="placeHolder"
    />
    <br />
    <span class="error">{{ errorMessage }}</span>
  </div>
</template>
<script lang="ts">
import {
  ref,
  defineComponent,
  watch,
  SetupContext,
  PropType
} from '@vue/composition-api'

import { Domain } from '~/types/index'
export default defineComponent({
  name: 'FormInput',
  props: {
    domainName: {
      type: Function as PropType<Domain>,
      required: true
    },
    labelName: {
      type: String,
      required: true
    },
    labelId: {
      type: String,
      required: true
    },
    inputType: {
      type: String,
      required: true
    },
    placeHolder: {
      type: String,
      required: true
    },
    inputName: {
      type: String,
      required: true
    }
  },
  setup(props, { emit }: SetupContext) {
    const value = ref('')

    const errorMessage = ref('')
    const isValid = ref(false)

    watch(
      value,
      () => {
        isValid.value = false
        errorMessage.value = ''
        emit('input', value.value)
        try {
          props.domainName.validation(value.value)
          isValid.value = true
        } catch (e) {
          errorMessage.value = e.message
        }
      },
      { lazy: true }
    )

    return {
      errorMessage,
      value,
      isValid
    }
  }
})
</script>
<style scoped>
.error {
  color: red;
}
</style>

親コンポーネントの記述

<template>
  <div class="container">
    <ValidationObserver
      ref="observer"
      v-slot="{ isValid }"
      :observer="observer"
    ><!-- 必須定型文 -->
      <FormInput
        v-model="forms.name"
        :domain-name="Name"
        :label-id="'name'"
        :label-name="'名前'"
        :input-type="'text'"
        :input-name="'name'"
        :place-holder="'名前を入力してください'"
      />
      <FormInput
        v-model="forms.email"
        :domain-name="EmailAddress"
        :label-id="'email'"
        :label-name="'メールアドレス'"
        :input-type="'text'"
        :input-name="'email'"
        :place-holder="'メールアドレスを入力してください'"
      />
      <button :disabled="!isValid">ボタン</button>
    </ValidationObserver>
    {{ forms }}
  </div>
</template>
<script lang="ts">
import { ref, reactive } from '@vue/composition-api'
import Name from '~/domain/Name'
import EmailAddress from '~/domain/EmailAddress'
import FormInput from '~/components/ValidationForm/ValidationFormInput.vue'
import ValidationObserver from '~/components/ValidationForm/ValidationObserver.vue'

export default {
  name: 'Index',
  components: { FormInput, ValidationObserver },
  setup() {
    const observer = ref<any>(null) // 必須
    const forms = reactive({
      name: '',
      email: ''
    })

    return { Name, EmailAddress, forms, observer }
  }
}
</script>

上記の方法では、ドメインオブジェクトのうちNameEmailAddressだけ読み込んで、それをリアクティブにして、子コンポーネントの:domainNameディレクティブで渡しています。子コンポーネントは受け取ったドメインオブジェクトを利用してvalidationメソッドを実行しています。

これらの子コンポーネントフォームは、ValidationObserverという名前のカスタムタグで囲まれていますが、これが子コンポーネントのすべてのvalidationに問題がないかをチェックしています。

(このObserverの役割はVeeValidate3と同じですね)

コメント 2020-05-31 142104.png

今回はformsで内容を受け取っています。

使用法

index.vueがその例になっていますが改めてご説明させていただきます。
まずバリデーションしたいドメインオブジェクトを作ります。これは普通の.ts/.jsファイルです。バリデーションロジックの書き方はvalidationメソッド内で例外を投げるように書きます。

ほぼ定型文になるのですが、ValidationFormInputコンポーネントを呼び出し、ValidationObserverとその定型文のプロパティでこれを囲みます。
これでValidationFormInputコンポーネントの内容が$ref経由で監視され、OKならisValidtrue、ダメならfalseになります。button要素の:disabledを利用して、ボタンコントロールができます。

また、ValidationFormInputなどはmixinなどで実装するといちいちimportで呼び出さずになります
(今後Nuxtでは自動importが標準で用意されるようなので、mixinも不要になるかもしれませんが)

終わりに

もう少し改良の余地がありますが、DDDや他のアーキテクチャによるコーディングを実践しながらリアルタイムバリデーションしたいという方の参考になれば幸甚の至り。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボルダリング中の姿勢を評価して数値化するアプリ

こんなの作りました

https://www.noboruu.club/#/

kKa1WDwtfH8GLdRlrJfd1590823130-1590823172.gif

  • スマホ推奨
  • スペック低いスマホだとうまく動かないかもしれない
  • ios未対応(厳密には動画が .mov の拡張子だと読み込めない)

WiFi環境推奨です。1分くらいの動画だと分析はだいたい1~2分くらいかかります。

分析できる動画には制限があって、アプリにも書いてあるとおりです。
試しにアプリの動き見るだけなら、ネットで見つけた人の画像を、だいたい10秒くらい、ゆらゆらさせて、登ってる風にして撮っても分析結果はハチャメチャですが、問題はないです。

分析の仕組み

動画に写っている人の姿勢を推定し、推定からわかるその人の骨格の座標を追跡することで、登ったボルダリングコースの経路を算出します。その経路がどの程度効率良かったかを数値化します。

効率の良さとは、イメージすると以下の画像になります。
- 赤色が効率の良い登り
- 黄色が効率の悪い登り

17292_paint.jpg
Pressfoto - jp.freepik.com によって作成された man 写真

ただ、もしかすると黄色の経路のほうがホールドが良く(持ちやすく)、力を消費せずに行けるかもしれません。もしかすると黄色の経路のほうがゴールまで早くたどり着けるかもしれません。総合的な効率の良さを評価するには、もう少し変数が必要ですね。
今回は経路の効率性の良さ、という角度で分析しているので、筋力の消費加減や時間は考慮していません。これらは今後の課題ですね。

プロトタイプを作成していたのときのGifをのっけておきます。スマホに写っている人の骨格(真ん中の水色の線)を分析しています。

効率の良い登りと判定されます。ほぼ真っすぐ登った想定です。
good.gif

効率の悪い登りと判定されます。ウネウネ登った想定です。
bad.gif

ちょっとボルダリング関連の論文を漁る

経路の効率性の算出式は論文から引っ張ってきています。
海外ではロッククライミング・ボルダリングに関する研究は意外にありました。
ほとんどの論文のアブストラクトに「マイナースポーツすぎて研究数が少ないよ、これ誰も研究してないから調べてみたよ」って書いてあるあたり、絶対数的には少ないんですかね。論文のアブストラクトってジャンルに限らず、唯一感出すための決り文句なんですかね、そんなに論文読まないので知らないです。

研究されている分類で行くと怪我系が多かったです。
面白いなって思ったのは、オブザベーション(コースを登る前にどんなコースかを観察する行為)がクライミングに与える影響を研究しているものもありました。今回は関係ないので割愛ですが、結論だけ言うとオブザベーションは大切でした。まる。
自分が調べてたのは、ボルダリング中の姿勢であったり、ホールドを保持(握っているという意味)している力の方向など、を初心者と経験者のサンプルから比較して差があるのか、を見るといったタイプの研究です。

技術

一番重要なのは姿勢推定で使用した PoseNet です。
PoseNet はクライアント側で動くので、クライアント側で動画は処理して骨格の判定をしています。アプリでは実際に動画をサーバーにアップロードしているわけではありません。アップロードしているのは、動画から判定した人の骨格のみです。プライバシー問題回避ですね(骨格から人を特定することができるなら、話は別ですが...)。

アプリケーションは、AWS Amplify + Vue で作っています。Firebase でも良かったんですが、Amazon の社員さんが激オシしていたものでちょっと使ってみました。普段の業務ではAWSの利用が多いですし、使い慣れたIAMで権限を管理できるのも魅力ですね。

アプリが目指す方向性

サッカー・バスケットボール・野球など様々な分野で数字の重要性がフォーカスされていますね。今あげた球技系とボルダリングで決定的に違うのは、そもそもボルダリングにはそこまで数値の種類がないです。なのでまず、その数値を作るところからです。
今の技術を使ったら、こんなのも数値化できるよ!という試みがこのアプリです。
数値化してみて、こんなの使えないよ、となったらそれまでです。解散ですね。

まとめ

できれば投げ銭してくれると嬉しいです。
アプリの分析結果画面に投げ銭ボタンをつけています。
アプリの分析結果画面に飛びたい方は ↓↓
https://www.noboruu.club/#/result?value=1

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js + ASP.NET Core + IdentityServer4 でログインしてみた(なおログアウトはしてないもよう)

React や Angular は Visual Studio にテンプレートがあるのでそれを使えばいいでしょう。
ドキュメントにも React と Angular についての解説があります。

ASP.NET Core でのシングルページアプリケーションの認証の概要

まぁ、最近は Google, Twitter, Facebook, Azure AD, etc... の外部認証プロバイダーを何かしら使うのがいいとは思うのですが、内部でユーザー管理するというのも需要はあると思うので、IdentityServer 4 で ASP.NET Core Identity 使って API のアプリ側(ASP.NET Core)でDBにユーザーを突っ込むパターンをしてみようと思います。

API サーバー側で重要なのは Startup.cs です。呼び出し元の Client のための CORS の設定と、認証の設定をしています。ついでに NSwag を使って Swagger にも対応する設定を追加しています。
RequireConfirmedAccount を false にして、メールアドレスの確認処理はスキップしています。ここを true にするとメール送信処理が必要になるので。

Startup.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Server.Data;
using TodoApi.Server.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace TodoApi.Server
{
    public class Startup
    {
        private static readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
        private IHostEnvironment _env;
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(name: MyAllowSpecificOrigins,
                    builder =>
                    {
                        // TODO: IsProduction のときの処理も追加する
                        if (_env.IsDevelopment())
                        {
                            builder.WithOrigins("http://localhost:8080")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
                        }
                    });
            });

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication()
                .AddIdentityServerJwt();

            services.AddControllersWithViews();
            services.AddRazorPages();

            services.AddSwaggerDocument();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            _env = env;
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            // CORS, Swagger, 認証の設定
            app.UseCors(MyAllowSpecificOrigins);
            app.UseOpenApi();
            app.UseSwaggerUi3();

            app.UseAuthentication();
            app.UseIdentityServer();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }
}

あとは、ApplicationDbContext, ApplicationUser クラスを定義して、接続文字列をユーザーシークレットか appsettings.json か appsettings.Development.json あたりに定義しておけば動きます。DB は Entity Framework のマイグレーション機能か何かで作っておきましょう。

認証ページの見た目とかをカスタマイズしたい場合はプロジェクトの右クリックメニューから追加で「新規スキャフォールディング アイテムの追加」で ID を追加すればファイルが沢山生成されるので、いじってカスタマイズできます。

image.png

本当、ドン引きするくらい作ってくれます。

image.png

そして、OidcConfigurationController を作ります。以下のような感じで

OidcConfigurationController.cs
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace TodoApi.Server.Controllers
{
    public class OidcConfigurationController : Controller
    {
        private readonly ILogger<OidcConfigurationController> _logger;

        public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger)
        {
            ClientRequestParametersProvider = clientRequestParametersProvider;
            _logger = logger;
        }

        public IClientRequestParametersProvider ClientRequestParametersProvider { get; }

        [HttpGet("_configuration/{clientId}")]
        public IActionResult GetClientRequestParameters([FromRoute] string clientId)
        {
            var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
            return Ok(parameters);
        }
    }
}

このコントローラーから返す情報は、appsettings.json や appsettings.Development.json で Clients のところに定義します。外部の SPA の場合は Profile に SPA を指定します。

appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "IdentityServer": {
    "Key": {
      "Type": "Development"
    },
    "Clients": {
      "todoapp-client": {
        "Profile": "SPA",
        "RedirectUri": "http://localhost:8080/authentication/login-callback",
        "LogoutUri": "http://localhost:8080/authentication/logout-callback"
      }
    }

  },
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=ApiTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  }
}

Authorize が属性をつけた Controller を適用に用意しておきましょう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace TodoApi.Server.Controllers
{
    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)],
            })
            .ToArray();
        }
    }
}

Vue.js 側

oidc-client を入れて以下のような感じでログイン処理を書きます。ちなみに、これは ASP.NET Core の React のプロジェクトテンプレートに含まれてる AuthorizeService.js をコピペして TypeScript にしました。

async signIn(state: SignInState): Promise<SignInResult> {
    await this.ensureUserManagerInitialized();
    assertIsDefined(this.userManager);
    try {
        const silentUser = await this.userManager.signinSilent(this.createArguments(null));
        this.updateState(silentUser);
        return this.success(state);
    } catch (silentError) {
        // User might not be authenticated, fallback to popup authentication
        console.log("Silent authentication error: ", silentError);

        try {
            if (this._popUpDisabled) {
                throw new Error('Popup disabled. Change \'AuthorizeService.js:AuthorizeService._popupDisabled\' to false to enable it.')
            }

            const popUpUser = await this.userManager.signinPopup(this.createArguments(null));
            this.updateState(popUpUser);
            return this.success(state);
        } catch (popUpError) {
            if (popUpError.message === "Popup window closed") {
                // The user explicitly cancelled the login action by closing an opened popup.
                return this.error("The user closed the window.");
            } else if (!this._popUpDisabled) {
                console.log("Popup authentication error: ", popUpError);
            }

            // PopUps might be blocked by the user, fallback to redirect
            try {
                await this.userManager.signinRedirect(this.createArguments(state));
                return this.redirect();
            } catch (redirectError) {
                console.log("Redirect authentication error: ", redirectError);
                return this.error(redirectError);
            }
        }
    }
}

_popUpDisabled というプロパティが AuthorizeService にあるので、false にしておきます。Edge で動かなくても知らん。
この AuthorizationService を使って signIn メソッドを呼び出すと画面が立ち上がってサインイン出来るようになります。

ルーターの beforeEach で認証が必要なメタデータがあったらサインインするような処理を組んでおくといい。

router.beforeEach(async (to, from, next) => {
  await store.auth.getUser();
  if (to.matched.some(x => !!x.meta?.requiresLogin)) {
    if (store.auth.isAuthenticated) {
      next();
    } else {
      const returnUrl = to.fullPath;
      const redirectUrl = `${ApplicationPaths.Login}?${QueryParameterNames.ReturnUrl}=${encodeURI(returnUrl)}`
      const result = await store.auth.signIn({
        returnUrl: redirectUrl,
      });
      switch (result.status) {
        case AuthenticationResultStatus.Redirect:
          break;
        case AuthenticationResultStatus.Success:
          await store.auth.getUser();
          next();
          break;
        case AuthenticationResultStatus.Fail:
          next(`/error?message=${result.message}`);
          break;
        default:
          throw new Error(`Invalid status result ${result.status}.`);
      }

    }
  } else {
    next();
  }
});

アクセストークンの取得はサインインが終わったタイミングでアクセストークンをゲットしておいて、axios の interceptors でヘッダーに付与するようにしておきました。

_axios.interceptors.request.use(
  function(config) {
    // Do something before request is sent
    if (store.auth.isAuthenticated) {
      config.headers = config.headers ?? {};
      config.headers['Authorization'] = `Bearer ${store.auth.accessToken}`;
    }
    return config;
  },
  function(error) {
    // Do something with request error
    return Promise.reject(error);
  }
);

正直クライアントサイドはたくさん書きすぎて説明しきれないので、コードを GitHub に上げてお茶をにごす

https://github.com/runceel/VueJsIdentityServer4

動きは以下のような感じです。Forecasts ページが認証が必要なページになります。
Forecasts ページに表示してるデータはサーバーの認証が必要な API からとってきています。

auth.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPと Vue.jsで簡易掲示板を作ろう〜その2( SCSS編)〜

初めに

前回にの
PHPと Vue.jsで簡易掲示板を作ろう〜その1(PHP編)〜
の続きです。
3つのパートに分けて
1.PHPでとりあえず動く掲示板を作る
2.SCSSで簡単なデザインを作る←今回はここ
3.Vue.jsで少しリッチな動きをつける

目次

・このパートでの完成イメージ
・HTML部分の変更の記述
・SCSSの記述
・まとめ

このパートでの完成イメージ

スクリーンショット 2020-05-31 6.45.08.png

・ボタンホバー時

スクリーンショット 2020-05-31 6.43.24.png

・テキスト入力欄フォーカス時

スクリーンショット 2020-05-31 6.45.36.png

・投稿されたメッセージのホバー時

スクリーンショット 2020-05-31 6.47.19.png

今回はSCSSでこれらのスタイルをつけていきます。

HTML部分の変更の記述

前回のHTML部分にclassと一つだけdivをつけていきます

<div class="container">
        <h1 class="title">簡易掲示板へようこそ(XSS対策済み)</h1>

        <!-- 投稿ボタン -->
        <form method="post" class="text-form"><!-- 中の投稿ボタンが押されたらPOSTのリクエストを送る --> <!-- NEW class追加 -->
            <input class="text" type="text" name="text"> <!-- NEW class追加 -->
            <input class="submit" type="submit" value="投稿する"> <!-- NEW class追加 -->
        </form>

        <div class="frame"> <!-- NEW 追加 -->
            <table class="contents"> <!-- NEW class追加 -->
                <?php foreach((array)$all_data as $post_data) : ?>
                <tr>
                    <form method="post">
                        <td class="text"> <!-- $post_data[2]は$post_text(テキストデータ) --> <!-- NEW class追加 -->
                            <?php echo h($post_data[2]); ?>
                        </td>
                        <td class="date"> <!-- $post_data[1]は$date(日付) --> <!-- NEW class追加 -->
                            <?php echo $post_data[1]; ?>
                        </td>
                    </form>
                </tr>
                <?php endforeach; ?>
            </table>
        </div>
    </div>

SCSSの記述

style.scss
.container {
    border: 1px solid black;
    text-align: center;

    // 入力欄と投稿ボタン
    & .text-form {

        // text入力欄
        & .text {
            width: 70%; 
            padding: 11px 15px;
            font-size: 16px;
            border-radius: 3px;
            border: 2px solid #ddd; 
            box-sizing: border-box;

            // フォーカス時
            &:focus {
                border: 2px solid #ff9900; 
                z-index: 10; //前面に表示
                outline: 0; // 元からある線を消す
            }
        }

        // 投稿ボタン
        & .submit {
            border-radius : 5%;
            font-size: 16px;
            text-align: center;
            cursor: pointer;
            padding: 9px 15px;
            background    : #000066;
            color         : #ffffff;
            transition    : .3s;
            box-sizing: border-box;

            // ホバー時
            &:hover {
                border: 2px solid #000066;
                color: #000066;
                background: #ffffff;

              }
        }
    }



    // 枠線
    & .frame {
        margin-top: 15px;
        border: 13px solid #993300;

        // メッセージと日付
        & .contents {
            width: 100%;
            background: #009900;

                // メッセージ
                & .text {
                    font-size: normal;
                    border: 1px solid white;
                    border-radius: 5px;

                    // ホバー時
                    &:hover {
                        background: white;
                    }

                }

                // 日付
                & .date {
                    font-size: normal;
                    float: right;
                }

        }
    }
}

まとめ

今回のパートではSCSSで少しだけ見た目をつけました!なんの計画もなしにスタイルを作成するとデザインが壊滅することが分かりました?
次回はVue.jsでリッチな動きをつけます。
この記事を見てくださったあなたの成長を応援します!!
ーーーー
次のパート:coming soon

この記事もいかがですか?

初心者に捧げる〜PHPを使って九九の表を作ろう〜
Vue.jsで作る!自動保存するToDoリスト~その1~
初心者に捧げるハンバーガーメニューの作り方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのアプリケーションをAWS上のNginxで公開する。(404エラーの罠)

Vue.jsのアプリケーションをAWS EC2上のNginxで公開する際に役立った情報の備忘録です。
Nginxのインストール等は省略します。

使用環境

Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type (無料使用枠)

Nginxの再起動コマンド

;再起動
sudo nginx -s reload

NginxのConfファイルの場所

;/etc/nginxフォルダへ移動
cd /etc/nginx

;nginx.confファイルを編集
sudo vi nginx.conf

Nginxのrootフォルダの場所

root(/usr/share/nginx/html)

server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

公開(RLoginを使用)

nginxが起動していれば、後はVue.jsでnpm buildしたdist直下のデータを、root(/usr/share/nginx/html)直下にコピーすればよい。

RLoginなどのツールを使ってAWSのEC2に接続し、SFTPファイルの転送を行うのがオススメ。
筆者も使ってみてRLoginはかなり使いやすいと感じた。

参考記事
Windowsターミナルソフト + Rlogin が最高すぎる!!

historyモードの罠

Vue.jsのhistoryモードには、ユーザが直接URLをたたくと404エラーが発生する罠がある。
nginx.confに一行追加すれば、問題なく解消する。

公式ドキュメントにもしっかり書かれている。
https://router.vuejs.org/ja/guide/essentials/history-mode.html#nginx

location / {
  try_files $uri $uri/ /index.html; ←追加
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む