资视就是力量 - Highcharts / Vue 做个记帐本 (上)

既然已经掌握了 Highcharts-Vue 的基本使用技巧,那今明两天打算带大家来实作一个「记帐本」,用一个比较完整的应用来完结这个系列,也算是一个学习成果的回顾。


记帐本实作

如下图所示,第一天打算先把我们记帐本的介面、图表及资料串接完成,画面大致如上,上方为新增帐目的表单元件,下方则是显示每日消费金额的柱状图,样式的话大家可以自由发挥,文章中将只会说明程序逻辑的部分。

https://ithelp.ithome.com.tw/upload/images/20201012/20125431vcFu0kQIMQ.png

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 请求,便会在资料库添加一笔新的帐目。

对了,为了把重点放在图表的应用上,有许多部分是被我省略的:

  1. 为了省去画面处理的逻辑,我故意使用 Form 表单,让资料送出时会自动刷新页面,这部分的使用体验可以再改善。
  2. 表单验证的部分也被我省略了,为了防止错误的资料格式存进资料库,再请各位自行撰写。
<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

可以纪录帐目後,就要让消费记录透过图表呈现出来了,而需求规格有以下几点:

  1. 每一个支出类别都是一组数据列,例如伙食、娱乐、交通..等。
  2. 图表为柱状图,不同数据列需要叠加,以便观察每日总消费的起伏。
  3. 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 应用,而明天我们将利用「事件属性」来为这个应用再添加一个小功能。


<<:  分支系列 - 7:合并发生冲突怎麽办?

>>:  [Day27] 监视股价 - Watcher

【课程推荐】2021/3/6~3/7、3/13~3/14 软件架构师技能培训班

课程目标 了解软件架构师所应具备的技能与素养,分析与规划软件架构模型,撰写符合国际标准的SAD (S...

Day1 渗透测试定义与资安服务比较

何谓渗透测试 以骇客的角度,针对企业的网路、系统、网站进行检测弱点与漏洞,并撰写一份渗透测试报告提...

D13/ 怎麽做翻卡片的动画 - Animation Part 2 & GraphicsLayer

今天大概会聊到的范围 Animation Modifier.graphicsLayer 上一篇讲到...

[C 语言笔记--Day19] Condition Code 帮忙做出 C 语言的 if 语法

大纲 C 语言中的 if x86-64 中的 condition code MSP430 的 sta...