vue移動助手實踐(三)————結合vue和localstorage的移動端記賬demo

katherine的小世界發表於2017-10-10

最近在用vue做一個小demo,做了幾個小小的功能模組,當做是學習練手吧。畢竟自己能力還是比較受限的,只能慢慢進步啦。最近就想著自己做一個移動端的記賬小demo,因為自己沒有弄後臺(其實是還沒去接觸學習哇咔咔),所以關於資料的儲存暫時就先用localstorage來儲存資料啦。

專案線上demo

專案線上演示demo(切換到移動端除錯模式哦)

專案github地址

專案github地址

最近在做的是小demo,這個是其中的一兩個頁面,是記賬模組。專門抽出來講。總的程式碼我會放在github上,今天講的這一部分程式碼主要是下面三個檔案內。

image.png
image.png

1 非常粗糙的草圖 (莫嫌棄哈哈哈,醜帥醜帥的字)

首先,上一下一一開始的設計圖(略醜)。我的目的是,可以記錄每一天的消費收入情況(包括消費專案 ,時間以及消費金額),通過選擇時間可以篩選每個月份的消費收入情況。每一天的收入消費情況為一個節點,點選每條條目都可以進入編輯頁面進行消費專案 ,時間以及消費金額的編輯。所以編輯專案頁面和新增專案頁面是同一個頁面。
對了,關於收入支出的icon列表,我一開始就已經定義了一些常見的icon。在icon list中的設定可以進行icon的編輯。

image.png
image.png

2 成果先看一步(UI有點醜,我後期要美化!!!!!)

test.gif
test.gif

3 專案的整個過程
問題難點:
  • 資料格式的定義:因為涉及到可以篩選出某一年的某一個月的所有資料,同時每一個月的某一天的資料也可以被篩選,所以資料格式的定義以及資料的儲存方式很重要。
  • 資料後期處理篩選:儲存了資料之後,如何根據年月來選擇篩選資料,同時要將屬於同一天的資料篩選出來。
  • 獲取當前被編輯的專案:因為點選每一條專案之後,都可以相應跳轉進去編輯頁面,當前的編輯頁面需要自動填充當前專案的資料,因為這涉及到兩個頁面之間的資料傳遞,我最後還是選擇了用localstorage 來儲存傳遞資料。

實踐開始:
1 資料儲存的形式:

我決定把每一條消費收入專案定義成一條這樣的資料形式,然後儲存在一個陣列裡。

list = [
     {type: "支出", money: "9999", date: "2017-10-6", icontype: "travel"},
     {type: "支出", money: "449", date: "2017-10-6", icontype: "travel"},
     {type: "支出", money: "799", date: "2017-10-8", icontype: "travel"},
     {type: "收入", money: "34", date: "2017-10-7", icontype: "pay"},
     {type: "支出", money: "9999", date: "2017-11-6", icontype: "travel"},
     {type: "收入", money: "34", date: "2017-11-6", icontype: "pay"},
     {type: "收入", money: "34", date: "2017-9-6", icontype: "pay"},
]
// 其中type是消費的型別,是收入還是支出。money是消費的金額。
date則是消費時間,icontype是我儲存的icon的名字,可以根據icontype的名字來顯示icon複製程式碼

接著就是資料的篩選了。上面的示例裡是有9月10月11月的資料,當然我們只需要的是某一個月份的資料,所以需要做一個filterData的方法來先過濾資料。

  // 通過年和月來篩選資料,返回篩選出來的資料。傳進去的data引數是要篩選的資料
    filterData (data, year, month) {
      let filterData = []
      for (let i = 0; i < data.length; i++) {
        let dateArr = data[i].date.split('-')
        if (dateArr[0] === year) {
          if (dateArr[1] === month) {
            filterData.push(data[i])
          }
        }
      }
      return filterData
    }複製程式碼

接著,就已經篩選出來了某一年某一月的消費資料了。我指定了年月是2017年10月,篩選出來之後資料如下所示:

list = [
     {type: "支出", money: "9999", date: "2017-10-6", icontype: "travel"},
     {type: "支出", money: "449", date: "2017-10-6", icontype: "travel"},
     {type: "支出", money: "799", date: "2017-10-6", icontype: "travel"},
     {type: "收入", money: "34", date: "2017-10-7", icontype: "pay"}
]複製程式碼

篩選出某一年某一個月的資料還是不夠的。因為我們需要向這樣的一種格式去顯示出來,就意味著需要將屬於同一天的資料儲存在一起

image.png
image.png

所以,我又寫了一個方法sortDatabyDate(),來將資料進行篩選組合,先看一下轉換之後的資料格式,如下所示:這個格式的好處就是,計算總的收入支出的時候,

list = [
// 這是2017-10-6的資料
    {date: "2017-10-6", income:34, outcome:'10377', sortindex:"6", list: [
       {type: "支出", money: "9999", date: "2017-10-6", icontype: "travel"},
       {type: "支出", money: "449", date: "2017-10-6", icontype: "travel"},
       {type: "支出", money: "799", date: "2017-10-6", icontype: "travel"}]},
// 這是2017-10-7的資料
    {date: "2017-10-6", income:34, outcome:'10377', sortindex:"6", list: [
       {type: "收入", money: "34", date: "2017-10-7", icontype: "pay"}]}
]複製程式碼

其實就是,將每一天的資料存在一個物件裡,然後其中的list就是這一天的每一條消費收入。其中的sortindex是為了排序使用的,就是將每天的資料儲存在list中之後,還需要按照日期從上到下排序,所以我會將這個月的日期,儲存在sortindex中。後續要排序也比較方便了。

 sortDatabyDate () {
      var map = []
      var dest = []
      var income = 0
      var outcome = 0
// 獲取當前年月的所有的資料
      for (let i = 0; i < this.filterConsumeData.length; i++) {
        var time = this.filterConsumeData[i].date
        if (this.filterConsumeData[i].type === '收入') {
          income = this.filterConsumeData[i].money
          outcome = 0
        } else {
          outcome = this.filterConsumeData[i].money
          income = 0
        }
// map是儲存這個月的日期的陣列,如果當前資料的時間不存在mapl裡面,就直接先建立一條資料
        if (map.indexOf(time) === -1) {
          dest.push({
            income: +income,
            outcome: +outcome,
            sortindex: time.split('-')[2],
            date: time,
            list: [this.filterConsumeData[i]]
          })
          map.push(time)
        } else {
// 當前這個資料的日期已經存在了,找到這條資料的索引,儲存進這條資料的list物件內就可以了
          for (let j = 0; j < dest.length; j++) {
            if (dest[j].date === time) {
              let oldIncome = dest[j].income
              let oldOutcome = dest[j].outcome
              dest[j].income = (+oldIncome) + (+income)
              dest[j].outcome = (+oldOutcome) + (+outcome)
              dest[j].list.push(this.filterConsumeData[i])
            }
          }
        }
      }
      console.log(dest, '這是排序之前的')
      // 再將得到的資料進行排序,**sortByfield方法可以根據物件的屬性進行排序**
      dest.sort(this.sortByfield('sortindex'))
      this.showConsumeList = dest  // 這是得到的最終的資料
      // 將得到的最終的資料,獲取當前的總收入和總支出
    // 一開始先賦值為0
      this.inCome = 0
      this.outCome = 0
      for (let i = 0; i < this.showConsumeList.length; i++) {
        this.inCome = (+this.inCome) + (+this.showConsumeList[i].income)
        this.outCome = (+this.outCome) + (+this.showConsumeList[i].outcome)
      }
    }複製程式碼

其中的排序方法,其實就是根據陣列物件中每一個物件中的sortIndex屬性來排序。這個可以結合陣列的sort()屬性來使用。
(友情連結)陣列物件根據物件排序 sort

// 其中field就是要排序的物件屬性,然後結合陣列的sort方法,直接使用就可以,。
// array.sort(sortByfield(屬性名))
   sortByfield (field) {
      return function (a, b) {
        return a[field] - b[field]
      }
    }複製程式碼

這樣寫下來就可以實現資料的轉換了。結合vue的for迴圈指令,就可以很愉快地將資料渲染出來了。是不是很棒。

2 如何獲取當前的編輯專案

細心的你會發現,就是在我點選每一個條目之後,會跳轉到編輯頁面。而且這個編輯頁面會自動渲染初始資料,那麼這個資料如何去傳遞呢?

我的做法
我是通過這個當前這個點選的條目的資訊,去獲取這個條目在總資料中的索引值,再將這個索引值用localStorage中儲存,跳轉到編輯頁面之後,只要從localstorage中獲取就可以了,如果編輯改動了,就直接在總資料中根據索引值去修改就可以了。

   editList (item) {
      this.$router.replace({path: '/moneyRecord'}) // 頁面跳轉到編輯頁面
      let totalData = JSON.parse(localStorage.getItem('list') || '[]')  //這是所有的條目資料
      // 點選進去之後就將資料傳遞到頁面
      this.editIndex = this.findIndex(totalData, item) // 自定義的一個方法,從所有的資料中獲取到index值。
      localStorage.setItem('editIndex', JSON.stringify(this.editIndex)) // 將index儲存下來
      localStorage.setItem('editItem', JSON.stringify(item))
    },複製程式碼

其中的 findIndex方法定義如下,使用了陣列自帶的findindex方法,可以自己去google一下,arr.findIndex(callback[, thisArg])

    findIndex (array, target) {
      let index = array.findIndex((item) => {
        return (item.date === target.date) && (item.type === target.type) && (item.icontype === target.icontype) && (item.money === target.money)
      })
      return index
    }複製程式碼

3 新增專案和編輯專案共用一個頁面

其實不管是編輯還是新增,都只是需要填寫下面的基本資訊而已,時間 金額 專案。
所以我是共用一個頁面的,唯一的區別就是編輯專案的時候需要資料初始化。那麼如何知道是編輯還是新增呢?

我的做法

前面我已經提到了用localstorage去儲存editIndex了。只要在進入當前頁面的時候,即monted的時候獲取這個editIndex是否存在,存在的話,就定義editType = 'edit',相反,就是editType = 'add'.
當然,在你離開頁面的時候,還需要將editIndex給remove掉。

所以,在moneyRecord頁面,我會刪除。

  mounted () {
    this.getIconList()
    let editItem = JSON.parse(localStorage.getItem('editItem') || '[]')
    if (editItem.length !== 0) {
//  編輯狀態,進行資料的初始化
      this.Edittype = 'edit'  // 當前是編輯狀態
      this.type = editItem.type
      this.selectedIcon = editItem.icontype
      this.consumeMoney = editItem.money
      this.pickerFormateValue = editItem.date
      if (this.type === '支出') {
        this.highlight = 'output'
        this.showIcon = this.outputIcon
      } else {
        this.highlight = 'income'
        this.showIcon = this.incomeIcon
      }
    } else {
//  新增狀態,將資料清空。
      this.Edittype = 'add'  // 當前是新增狀態
      this.pickerFormateValue = this.setDateFormate(new Date())
      this.highlight = 'output'
      this.showIcon = this.outputIcon
      this.selectedIcon = ''
      this.consumeMoney = ''
    }
  },
beforeDestroy () {
    bus.$off('get', this.myhandle)
    localStorage.removeItem('editItem')
    localStorage.removeItem('editIndex')
  }複製程式碼

4 實現icon的開關設定

可以手動控制icon的顯示和隱藏。我會先初始化定義一些icon的資料,初始化儲存在localstorage中。然後通過監聽資料的變化,來實時變化儲存的資料。因為要監聽到的是物件屬性值的變化,所以需要深度監聽。

// 通過type 中的狀態來判斷是否顯示icon
    getIconList () {
      this.outputIcon = JSON.parse(localStorage.getItem('outputIcon') || '[]')
      this.incomeIcon = JSON.parse(localStorage.getItem('incomeIcon') || '[]')
      console.log(this.incomeIcon, this.outputIcon, '這是新的輸出icon', '這是新的輸入icon')
      if (this.incomeIcon.length === 0) {
        this.incomeIcon = [
      {name: 'pay', title: '薪資', iconClass: 'icon-zhifuxinshui', type: true},
      {name: 'getmoney', title: '獎金', iconClass: 'icon-jiangxuejinv', type: true},
      {name: 'shorttime', title: '兼職', iconClass: 'icon-jianzhizhongdiangong', type: true},
      {name: 'rate', title: '投資收益', iconClass: 'icon-touzihouhuodeshouyi', type: true}]
        this.outputIcon = [
      {name: 'shopping', title: '購物', iconClass: 'icon-gouwu', type: true},
      {name: 'money', title: '理財', iconClass: 'icon-licai', type: true},
      {name: 'traffic', title: '交通', iconClass: 'icon-jiaotong', type: true},
      {name: 'fun', title: '娛樂', iconClass: 'icon-yule', type: true},
      {name: 'meal', title: '餐飲', iconClass: 'icon-icon', type: true},
      {name: 'travel', title: '旅行', iconClass: 'icon-lvyou', type: true},
      {name: 'medical', title: '醫療', iconClass: 'icon-yiliao', type: true},
      {name: 'specialMoney', title: '禮金', iconClass: 'icon-lijin', type: true},
      {name: 'beauty', title: '美容', iconClass: 'icon-meirong', type: true}]
        localStorage.setItem('outputIcon', JSON.stringify(this.outputIcon))
        localStorage.setItem('incomeIcon', JSON.stringify(this.incomeIcon))
      }
    }

// 監聽資料的變化,資料變化,就重新儲存資料。
  outputIcon: {
      handler: function (val) { localStorage.setItem('outputIcon', JSON.stringify(val)) },
      deep: true
    },
    incomeIcon: {
      handler: function (val) { localStorage.setItem('incomeIcon', JSON.stringify(val)) },
      deep: true
    }複製程式碼

test.gif
test.gif

vue 的主要核心就是資料驅動,做這個專案的時候就深刻地意識到,事先定義好比較好的資料結構是多麼重要。一旦資料結構定義好之後,再進行後期的資料處理,就可以很好地根據資料進行渲染了。
所以在這裡資料的後期處理就很重要,掌握好陣列的一些方法,像sort findIndex 以及split等方法都很重要。

是時候學點後端的東西啦。。。。。

相關文章