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