---
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檔案拉到這
}
```
-->