vue移动助手实践(三)————结合vue和localstorage的移动端记账demo

1,458 阅读8分钟

最近在用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等方法都很重要。

是时候学点后端的东西啦。。。。。