わぁい!どうぶつの森 カブ損益ツールをVueで作ったよー!


Tl;DR

  • Vue Cliを使ってどうぶつの森 カブ損益ツールを作ったよ
  • できあがったのはこちらだよ
  • GitHubはこれだよ

あなた誰?

はじめまして。葉月ゆき(@peridot_snow08)、初投稿です。
フロント未満コーダー以上の存在でエンジニアをやりつつ都内でほそぼそしてます。

何やったの?

前から興味があったVueで簡単などうぶつの森 カブ損益ツールを作りました!

きっとまだ不完全だと思われる部分もあるけど、公開はできた!
なのでやってきたことを振り返ろうと思います。

振り返り

各値の入力と結果の表示

始めにここを作ったので、数値入力と結果が親子関係になってないです。
見返すと、親子関係にしたほうがよかったかなぁって思います。

BellInput.vue
<template lang="pug">
  div.bellInput
    div.trade
      .buyKabu.kabuPrice
        label(for='buyBell') 買値
        div.kabuPriceInput
          input#buyBell(type='number', v-model.number='buyPrice' placeholder="100")
          span ベル
      .sellKabu.kabuPrice
        label(for='sellBell') 売値
        div.kabuPriceInput
          input#sellBell(type='number', v-model.number='sellPrice' placeholder="100")
          span ベル
    .kabuNumber 
      label(for='kabuNumber') 購入数
      .kabuPriceInput
        input#kabuNumber(type='number', v-model.number='kabuNumber' placeholder="100")
        span カブ
    h2.result 購入結果
    table
      tr
        td.first 購入合計
        td 
          span.priceTxt {{buyTotal | numberGrouping}}
          span ベル
      tr
        td.first 買取合計
        td 
          span.priceTxt {{sellTotal | numberGrouping}}
          span ベル
      tr.borderNone
        td.first カブ損益
        td 
          span(:class="{active : kabuTotal < 0}").priceTxt {{kabuTotal | numberGrouping}}
          span ベル
    Tanukichi(:kabuTotal="kabuTotal")
    StrageBtn(:buy="buyPrice" :sell="sellPrice" :total="kabuNumber")
</template>

<script>
import Tanukichi from './Tanukichi.vue'
import StrageBtn from './StrageBtn.vue'
export default {
  name: 'BellInput',
  components: {
    Tanukichi,
    StrageBtn
  },
  data(){
    return {
       buyPrice: '',
       sellPrice: '',
       kabuNumber:'',
    }
  },
  mounted() {
    if(localStorage.length > 0) {
      this.buyPrice = localStorage.getItem('buy')
      this.sellPrice = localStorage.getItem('sell')
      this.kabuNumber = localStorage.getItem('total')
    }
  },

  computed: {
    buyTotal(){
      return this.buyPrice * this.kabuNumber
    },
    sellTotal(){
      return this.sellPrice * this.kabuNumber
    },
    kabuTotal(){
      return this.sellTotal - this.buyTotal
    },
  },
  filters: {
    numberGrouping(price){
      return price = price.toLocaleString()
    }
  }

}
</script>

各値はv-model、結果computedで計算し、Filterを使って3桁区切りをしています。
computedで桁区切りを行うと型変換が起こってしまうため、propsで渡すと文字列として渡してしまいます。
これだと後で支障きたすため、Filterを使いマスタッシュの中で桁区切りを行ってます。
コードの見たわしが若干悪くなるけど、地味に便利だったので採用。
各計算結果と入力値は使うので、propsで子Componentに渡してあげてます。

たぬきちのアドバイス

各値を入力すると、たぬきちがアドバイスをくれます。
たぬきち曰くプラマイ0は運命らしいです。

BellInput.vue
<template lang="pug">
  div.tanukichi
    p.name たぬきち
    vue-typer(
      :text="tanukichiTalk"
      :repeat='0'
    )
</template>

<script>
/* eslint no-irregular-whitespace: ["error", {"skipTemplates": true}] */
import { VueTyper } from 'vue-typer'
export default {
  name: 'Tanukichi',
  components: {
    VueTyper
  },
  props: {
    kabuTotal: {
      type: Number
    }
  },
  data() {
    return {
      tanukichiTalk:`数値を入力をしてだなも。\n3つの数字をいれると、自動的に金額がでるだなも!`
    }
  },
  watch: {
    kabuTotal(newTotal){
      if(newTotal > 0){
        this.tanukichiTalk = `今 カブを売ると儲けでるだなも!\n売り値が上がるともっと儲けることができるから、もう少し待ってみるだなも。`
      } else if( newTotal < 0) {
        this.tanukichiTalk = `あわわわ! 損がでちゃただなも!\n明日になったらカブ価が回復するかも…\n今は売らずに待つのがよいだなも!`
      } else {
        this.tanukichiTalk = `プラスマイナス0だなも。\n損をしなくて済むから今売るといいだなも!`
      }
    }
  },
}
</script>

パッ切替わると味気がないので、ちゃんと会話ウィンドウっぽく喋ってくれるように、タイピングエフェクトにはVueTyperを使いました。
株価の損益の結果の値を親からもらい、たぬきちのセリフを変えてます。
セリフの切り替えをmethodsでやろうとしましたが、処理が重くなりそうなので、watchで処理。

ヘルプのモーダル

ヘルプの部分を押すと、ヘルプのモーダルが開きます。
Componentを別にし、App.vue経由でModalを表示させるかを制御してます。

Header.vue
<template lang="pug">
  transition(name="modal")
    div
      div.modalOverlay
        span.modalCloseBtn
        div.modalContents
          div.useHead 使い方
          ul.howToUse
            li 買値・売値・購入数の3つを入力すると、購入結果に自動で計算結果が出力されます。購入結果がマイナスになると、損益が赤く表示されます。
            li 【結果を保存する】のボタンを押すと、ブラウザに買値・売値・購入数の入力値が保存され、次回アクセス時に自動で挿入されます。
            li 【保存データを破棄】を押すとブラウザに保存されたデータは破棄されます。入力結果はそのまま残りますので、日曜日のタイミングなどに使うといいかもです。
          p 開発の要望などは<a href="https://twitter.com/peridot_snow08" target="_blank">葉月ゆき</a>のTwitterにてお寄せください。
          button(@click="modalClose") ヘルプを閉じる
</template>

<script>
export default {
  name:'Modal',
  methods: {
    modalClose(){
      this.$emit('modalClose')
    }
  },
}
</script>
App.js
<template lang="pug">
  div#app
    Header(@modalOpen = 'toggleModalFlag')
    BellInput
    Modal(v-if="modalFlag" @modalClose = 'toggleModalFlag')
</template>

<script>
import Header from './components/Header.vue'
import BellInput from './components/BellInput.vue'
import Modal from './components/Modal.vue'
export default {
  name: 'App',
  components: {
    Header,
    BellInput,
    Modal
  },
  data(){
    return {
      modalFlag: false
    }
  },
  methods: {
    toggleModalFlag(){
      this.modalFlag = !this.modalFlag
    }
  },
}
</script>

子から親へは基本イベントしか渡せないというので、Headerでクリックイベントを発火させて、App.jsの切り替えメソッドに伝搬させ、それをpropで真偽値を渡すということをしてます。
クローズのときもApp.jsの切り替えメソッドまでは同じ。

こんなことをやりつつ、損益ツールは動かして作りました。
ただ…モーダルを表示時にactive-enterが入らないのでトランジションが動きません…。

完成した感想

基礎を叩き込むまで随分時間はかかってしまいましたが、理解をするとこんなにも便利フレームワークがあるのか!一気に世界が開けた気がしました。特にデータバインディングの利便性はとってもすごい。
ツール自体まだ甘いところはありますが、インプットだけではと思い、こんなツールを作った次第です。誰かの役に経てばいいな。
Vue自体に特に大きな制約もないため、これからどんどんいろんなものを作って行きたいと思います。

最後に

お友達がいないのでお友達募集中です。
Twitterやってるので、お友達になってください…