學到新資視了 - Highcharts / Vue 做個記帳本 (上)
既然已經掌握了 Highcharts-Vue 的基本使用技巧,那今明兩天打算帶大家來實作一個「記帳本」,用一個比較完整的應用來完結這個系列,也算是一個學習成果的回顧。
記帳本實作
如下圖所示,第一天打算先把我們記帳本的介面、圖表及資料串接完成,畫面大致如上,上方為新增帳目的表單元件,下方則是顯示每日消費金額的柱狀圖,樣式的話大家可以自由發揮,文章中將只會說明程式邏輯的部分。
1.安裝 Json-Server
為了可以讓記帳本真的可以記帳,我們會需要資料庫來紀錄帳目,而我選擇使用的是 Json-Server,它讓我們可以用一個 Json 檔來作為簡易的資料庫,而且內建基本的 REST API 可以使用,這樣我們就不用真的架一個後端伺服器了,在終端機輸入下方指令就可以全域安裝了。
npm install -g json-server
安裝完成後新增一個 db.json
,然後可以先手動新增一筆帳目,檔案內容如下:
{
"accounts": [
{
"id": 1,
"category": "伙食",
"amount": "50",
"date": 1602028800000,
"description": "早餐"
}
]
}
檔案備妥後,在終端機輸入以下指令就可以啟動 json-server
,這時候到 http://localhost:3000/accounts
,就可以看到剛剛建立的資料了,未來只要是開啟伺服器的狀態就可以使用 API 來讀寫 db.json
的資料了。
json-server db.json
2.安裝 axios
資料庫有了之後,接下來在專案資料夾底下輸入終端機指令來安裝 axios
,它可以讓我們更便捷的處理 XMLHttpRequest。
npm install axios
安裝好後到 main.js
添加以下程式碼來引用它,這樣我們就可以在元件裡使用 this.axios.get()
來呼叫 API 了。
import Vue from "vue";
import axios from "axios";
Vue.prototype.axios = axios;
3.Highcharts 全域設定
由於這次的應用會使用到日期座標,所以我們可以來調整一下「語言設定」,設定方法是在 main.js
引入原生 Highcharts,然後一樣是呼叫 setOptions()
。
import Vue from "vue";
import Highcharts from "highcharts";
Highcharts.setOptions({
lang: {
shortMonths: ["1月", "2月", ..., "12月"],
weekdays: ["星期日", "星期一", ..., "星期六"],
},
// 也可增加其他你像要的全域設定
credits: { enabled: false },
colors: [...]
});
4.表單元件 - LedgerForm.vue
前置作業完成後就可以來開發元件了,首先是上方用來新增帳目的表單元件。新增一個檔案內容如下的元件,其中四個表單欄位剛好對應資料庫的內容,而點擊按鈕觸發的 addItem
就是用 axios
送出 post
請求,便會在資料庫添加一筆新的帳目。
對了,為了把重點放在圖表的應用上,有許多部分是被我省略的:
- 為了省去畫面處理的邏輯,我故意使用 Form 表單,讓資料送出時會自動刷新頁面,這部分的使用體驗可以再改善。
- 表單驗證的部分也被我省略了,為了防止錯誤的資料格式存進資料庫,再請各位自行撰寫。
<template>
<form>
<fieldset>
<legend>新增帳目</legend>
<div class="input-group">
<label>日期</label>
<input type="date" v-model="date">
<label>分類</label>
<select v-model="category">
<option value="伙食">伙食</option>
<option value="交通">交通</option>
<option value="生活">生活</option>
<option value="帳單">帳單</option>
<option value="娛樂">娛樂</option>
</select>
<label>金額</label>
<input type="number" v-model.number="amount">
</div>
<div class="input-group">
<label>說明</label>
<input type="text" v-model="description">
</div>
<button @click="addItem">+</button>
</fieldset>
</form>
</template>
<script>
export default {
data() {
return {
date: "",
category: "伙食",
amount: 0,
description: ""
}
},
methods: {
addItem() {
this.axios.post("http://localhost:3000/accounts", {
date: new Date(this.date).getTime(),
category: this.category,
amount: this.amount,
description: this.description
})
}
}
}
</script>
5.圖表元件 - LedgerChart.vue
可以紀錄帳目後,就要讓消費記錄透過圖表呈現出來了,而需求規格有以下幾點:
- 每一個支出類別都是一組數據列,例如伙食、娛樂、交通..等。
- 圖表為柱狀圖,不同數據列需要疊加,以便觀察每日總消費的起伏。
- X軸為日期座標軸,並且顯示只顯示從今天算起的前七天。
避免模糊焦點,這次圖表只做到近七天的資料顯示,有興趣的話你也可以根據自己你想法改善這個範例。
根據需求,我們先準備好需要的資料,包括儲存今日時間戳的 todayTimeStamp
,儲存 API 資料的 fetchData
,以及圖表設定 chartOptions
。
data() {
return {
todayTimeStamp: 0,
fetchData: [],
chartOptions: {
chart: { type: "column" },
title: { text: "每日消費" },
tooltip: {
shared: true,
headerFormat: "{point.key:%Y/%m/%d %A}<br/>",
valuePrefix: "NT$"
},
xAxis: {
type: "datetime",
categories: [],
labels: { format: "{value:%b%d}日" },
},
yAxis: {
title: undefined,
labels: { format: "NT$ {value}" },
},
plotOptions: {
series: { stacking: "normal" }
},
series: [],
}
};
}
而API資料我們需要再 created
時先去取得,順便把今天的時間戳儲存起來,方便我們之後計算近七天的時間。
created() {
// 取得當日的時間戳
let now = new Date().getTime();
this.todayTimeStamp = now - now % 86400000;
// call json-server api
this.axios.get("http://localhost:3000/accounts").then((response) => {
this.fetchData = response.data;
});
},
有了這些資料後,就可以透過 computed
來將資料處理成我們所需的格式了。
computed: {
// 利用當日時間戳來計算X軸所需的 categories 陣列
xAxisCategories() {
return Array(7).fill(0).map((date ,index) => {
return this.todayTimeStamp - 86400000 * index
}).reverse()
},
// 抓出所有不重複的支出類別
expendType() {
let allType = this.fetchData.map(item => item.category);
return Array.from(new Set(allType));
},
// 把 API 資料 map 成我們需要的格式
seriesData() {
return this.expendType.map(cate => {
let itemByCate = this.fetchData.filter(item => item.category === cate);
let points = this.xAxisCategories.map(date => {
return itemByCate.reduce((acc, item) => {
return item.date === date ? acc + Number(item.amount) : acc
}, 0)
})
return { name: cate, data: points };
});
},
// 將圖表設定和處理完的資料合併
options() {
let options = Object.assign(this.chartOptions, {});
options.series = this.seriesData;
options.xAxis.categories = this.xAxisCategories;
return options;
}
}
最後把 options
傳入圖表,並且記得把今天新增的兩個元件掛載到 App.vue
上,就可以看到消費紀錄呈現在圖表上囉。
<template>
<div class="chart-container">
<div v-if="!fetchData.length" class="noData">無任何消費記錄</div>
<highcharts v-else :options="options"></highcharts>
</div>
</template>
辛苦各位了,今天的篇幅比較長,不過跟著今天一步步的流程下來,終於完成了一個比較完整的 Highcharts-Vue 應用,而明天我們將利用「事件屬性」來為這個應用再添加一個小功能。
- 此篇文章為「iT邦幫忙鐵人賽」參賽文章,同步發表於 iT邦幫忙 -