最近在用vue做一个小demo,做了几个小小的功能模块,当做是学习练手吧。毕竟自己能力还是比较受限的,只能慢慢进步啦。最近就想着自己做一个移动端的记账小demo,因为自己没有弄后台(其实是还没去接触学习哇咔咔),所以关于数据的存储暂时就先用localstorage来存储数据啦。
项目在线demo
项目github地址
最近在做的是小demo,这个是其中的一两个页面,是记账模块。专门抽出来讲。总的代码我会放在github上,今天讲的这一部分代码主要是下面三个文件内。
image.png1 非常粗糙的草图 (莫嫌弃哈哈哈,丑帅丑帅的字)
image.png首先,上一下一一开始的设计图(略丑)。我的目的是,可以记录每一天的消费收入情况(包括消费项目 ,时间以及消费金额),通过选择时间可以筛选每个月份的消费收入情况。每一天的收入消费情况为一个节点,点击每条条目都可以进入编辑页面进行消费项目 ,时间以及消费金额的编辑。所以编辑项目页面和新增项目页面是同一个页面。
对了,关于收入支出的icon列表,我一开始就已经定义了一些常见的icon。在icon list中的设置可以进行icon的编辑。
2 成果先看一步(UI有点丑,我后期要美化!!!!!)
test.gif3 项目的整个过程
问题难点:
- 数据格式的定义:因为涉及到可以筛选出某一年的某一个月的所有数据,同时每一个月的某一天的数据也可以被筛选,所以数据格式的定义以及数据的存储方式很重要。
- 数据后期处理筛选:存储了数据之后,如何根据年月来选择筛选数据,同时要将属于同一天的数据筛选出来。
- 获取当前被编辑的项目:因为点击每一条项目之后,都可以相应跳转进去编辑页面,当前的编辑页面需要自动填充当前项目的数据,因为这涉及到两个页面之间的数据传递,我最后还是选择了用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所以,我又写了一个方法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
vue 的主要核心就是数据驱动,做这个项目的时候就深刻地意识到,事先定义好比较好的数据结构是多么重要。一旦数据结构定义好之后,再进行后期的数据处理,就可以很好地根据数据进行渲染了。
所以在这里数据的后期处理就很重要,掌握好数组的一些方法,像sort findIndex 以及split等方法都很重要。
是时候学点后端的东西啦。。。。。
网友评论