---
# System prepended metadata

title: '10/05 VueJS 教學筆記: computed'
tags: [FGO, VueJS]

---

---
title: '10/05 VueJS 教學筆記: computed'
disqus: hackmd
---

10/05 VueJS 教學筆記: computed
===
:::info
本次以現在手機遊戲中最夯最坑又最香的 Fate/Grand Order 友情點數抽卡為例子，來試著如何利用 computed 和 Math.floor()、Math.random() 重現該介面功能(不考慮複雜的統計與人為控制機制)。
:::

綱要

[TOC]


![](https://i.imgur.com/bWMbNEp.png)

[Codepen範例](https://codepen.io/fortes-huang/pen/OJLeZGp)

由於友情點數抽卡體感上機率較單純，所以只使用Math.random()來模擬。

**Context:**

1. 抽一次是 200 點，十連抽 2000 點
2. 每一張卡可重複隨機出現

**Force:**

1. 抽卡介面仿照實際遊戲內容呈現，可選擇單抽或十連抽
2. 依照現有友情點數，需顯示可抽次數 (小數點無條件捨去)
3. 若友情點數不足，顯示「友情點數不足」、Button 狀態不可操作
4. 若點數不足夠抽取十次，Button顯示最多可抽取多少次

Vue2.0 computed 用法與用例
---

computed 是用來處理透過些許計算後，回傳資料的一個屬性。
當頁面初始化、`data()`中的資料發生變化時就會被執行。
若沒有指定任何的 `get()` 與 `set()` ，預設就是`get()`來取得計算後的結果。
用一個最基本的例子解釋：

[Codepen範例](https://codepen.io/fortes-huang/pen/wvvqavQ)

```htmlmixed=
<template>
  <div id="app">
  <p>Result 1: {{ defaultResult }}</p>
  <p>Result 2: {{ calcResult }}</p>
  </div>
</template>
```
```javascript=
data() {
  return {
    numA: 100,
    numB: 200,
    numC: 500,
    numD: 500
  }
}
computed: {
  defaultResult() {
    return this.numA + this.numB // 很直接的get()，結果會是'300'
  },
  calcResult: {
    get() {
      return this.numC + this.numD // 同樣直接return，結果'1000'
    },
    set(newVal) {
      this.numC = this.numC + 1000
      console.log('new value: ', newVal) // 印出setter計算後新的值
    }
  }
},

mounted() {
  // 給值觸發calcResult的setter，結果將會是'2000'
  this.calcResult = 1000
}

```

題解步驟
---

template:

```htmlmixed=
<div class="game_panel">
  <ul class="game_info">
    <li>友情點數: {{ account.friendShip }} 可抽次數: {{ account.playableCount }}</li>
  </ul>
  <!--beforePick為起始畫面，顯示兩個Button-->
  <div v-if="beforePick" class="display">
    <el-button class="pickup" :disabled="buttonDisable" @click="handlePickupDisplay(button.one.count)">{{ button.one.msg }}</el-button>
    <el-button v-if="multiPickButton" class="pickup" @click="handlePickupDisplay(button.mult.count)">{{ button.mult.msg }}</el-button>
  </div>
  
  <!--pickStart為抽卡結果，顯示最多十張卡片結果-->
  <div v-if="pickStart" class="pickupBlock">
    <div class="display" data-row-count="5">
      <div v-for="(item, index) in pickPool " class="data_row card_obj">
        <span class="card">
          <p>{{ item.name }}</p>
        </span>
      </div>
    </div>
    <div class="btn_outer">
      <el-button class="pickup" @click="handlePickupDisplay(0)">繼續召喚</el-button>
    </div>
  </div>
</div>
```

先處理點數消費與可抽次數的顯示，再把卡池內容以物件形式簡單的定義出來：

```javascript=
data() {
  return {
    // 抽卡時的卡片資料存放在這
    pickPool: [],
    // 卡池
    cardPool: [ 
      { class: 'saber', name: '貝德維爾', rare: 3 },
      { class: 'mystic', name: '龍脈', rare: 3 },
      { class: 'mystic', name: '奇蹟求道者', rare: 3 },
      { class: 'saber', name: '睿智的大火', rare: 3 },
      { class: 'berserker', name: '清姬', rare: 3 },
      { class: 'berserker', name: '大流士三世', rare: 3 },
      { class: 'rider', name: '美杜莎', rare: 3 },
      { class: 'rider', name: '布狄卡', rare: 3 },
      { class: 'caster', name: '睿智的大火', rare: 3 },
      { class: 'caster', name: '美狄亞', rare: 3 },
      { class: 'lancer', name: '庫夫林', rare: 3 },
      { class: 'archer', name: '阿拉什', rare: 1 },
      { class: 'caster', name: '安徒生', rare: 2 }
    ]
  }
}

```

再補上此例玩家帳號中，需要提取的資訊：

```javascript=
data() {
  return {
    // 卡池內容
    // ...略
    account: {
      friendShip: 20100, // 帳號中當前保有的友情點數
      playableCount: 0, // 可抽卡次數，初始值 0
    },
  }
}

```

加上轉場替換component時的狀態：

```javascript=
data() {
  return {
    // 卡池內容
    // ...略
    // account
    beforePick: true,
    pickStart: false,
  }
}  
```

抽一次的花費是200點，而單抽的Button需要有disabled的開關、十連抽Button因為可抽數低於兩次會消失，也必須給個v-if顯示調用條件：

```javascript=
data() {
  return {
    // 卡池內容
    // ...略
    // account
    cost: 200, // 抽卡花費固定200
    buttonDisable: false, // 不足抽一次時單抽Button顯示為disabled
    multiPickButton: true, // 是否顯示十連抽Button的狀態
    button: {
      one: { // 單抽Button的顯示資訊
        msg: '',
        count: 0
      },
      mult: { // 十連抽Button的顯示資訊
        msg: '',
        count: 0
      }
    }
  }
}
```

按下抽卡Button的事件：

```javascript=
methods: {
  handlePickupDisplay(val) {
    this.beforePick = !this.beforePick
    this.pickStart = !this.pickStart
    this.buttonMsg
    this.pickCard(val)
    this.account.friendShip = this.account.friendShip - (this.cost * val)
    if (val == 0) {
      this.pickPool = []
    }
  },
  
  pickCard(val) {
    let max = val
    let arrIndex = 0

    for (let i = 0; i < val; i++) {
      if (val < this.cardPool.length) {
        max = this.cardPool.length
      }
      arrIndex = Math.floor(Math.random() * max)
      this.pickPool.push(this.cardPool[arrIndex])
      console.log(arrIndex)
    }
  }
}
```

特別注意一下，`Math.random()` 本身回傳的結果會是隨機的多位小數，所以還必須用`Math.floor`來取整數後丟進`this.pickPool`

```javascript=
arrIndex = Math.floor(Math.random() * max)
this.pickPool.push(this.cardPool[arrIndex])
```

```javascript=
computed: {
  buttonMsg() {
    let cost = this.cost // cost固定200
    let totalPoint = this.account.friendShip
    let multCount = Math.floor((totalPoint / this.cost))
    let oneCount = 0

    // 別忘了預設值重置
    this.buttonDisable = false
    this.multiPickButton = true
    this.account.playableCount = multCount

    // 判斷點數是否足夠抽一次 (用三元式)
    totalPoint >= cost ? oneCount = 1 : oneCount = 0
    this.button.one.count = oneCount
    this.button.one.msg = `${oneCount + ' 回召喚'}`

    // 假如右側Button不顯示且友情點數不足200則按鈕訊息顯示 "友情點數不足"
    if (totalPoint < cost && oneCount == 0) {
      this.button.one.msg = '友情點數不足'
      this.buttonDisable = true
    }

    // 判斷點數是否足夠抽十次，如果不夠十次，剩餘次數是否大於兩次?

    if (multCount >= 10) {
      this.button.mult.msg = `${'10 回召喚'}`
      this.button.mult.count = 10
    } else if (multCount < 2) {
      this.multiPickButton = false
    } else {
      this.button.mult.count = multCount
      this.button.mult.msg = `${multCount + ' 回召喚'}`
    }

    // 小於兩次時右側按鈕不顯示
    if (multCount < 2) {
      this.multiPickButton = false
    }
  }
}

created() {
// 剛進入頁面時先運算一次computed中的buttonMsg()
  this.buttonMsg
}
```



###### tags: `VueJS` `FGO`



<!-- [GitHub範例](https://github.com/fortes1219/vue_0803/blob/0803/src/router.js)
:::info
注意，假如你也有跟我一樣要把 Home 設定起始畫面就是 Dashboard 才跟著做，如果要改成獨立分頁，寫在跟 home 平行同層物件下即可。
:::

```javascript=
{
  path: 'newpage' //路徑。也就是網址打什麼後綴字會出現
  name: 'newPage', //名稱，router切換頁面$router.push('newPage')會使用這個名字  
  component: () => import './views/newPage.vue'  // 將新增頁面的vue檔案拉到這
} 
```
-->


