最近在用vue做一個小demo,做了幾個小小的功能模組,當做是學習練手吧。畢竟自己能力還是比較受限的,只能慢慢進步啦。最近就想著自己做一個移動端的記賬小demo,因為自己沒有弄後臺(其實是還沒去接觸學習哇咔咔),所以關於資料的儲存暫時就先用localstorage來儲存資料啦。
專案線上demo
專案github地址
最近在做的是小demo,這個是其中的一兩個頁面,是記賬模組。專門抽出來講。總的程式碼我會放在github上,今天講的這一部分程式碼主要是下面三個檔案內。
1 非常粗糙的草圖 (莫嫌棄哈哈哈,醜帥醜帥的字)
首先,上一下一一開始的設計圖(略醜)。我的目的是,可以記錄每一天的消費收入情況(包括消費專案 ,時間以及消費金額),通過選擇時間可以篩選每個月份的消費收入情況。每一天的收入消費情況為一個節點,點選每條條目都可以進入編輯頁面進行消費專案 ,時間以及消費金額的編輯。所以編輯專案頁面和新增專案頁面是同一個頁面。
對了,關於收入支出的icon列表,我一開始就已經定義了一些常見的icon。在icon list中的設定可以進行icon的編輯。
2 成果先看一步(UI有點醜,我後期要美化!!!!!)
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"}
]複製程式碼
篩選出某一年某一個月的資料還是不夠的。因為我們需要向這樣的一種格式去顯示出來,就意味著需要將屬於同一天的資料儲存在一起
所以,我又寫了一個方法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
}複製程式碼
vue 的主要核心就是資料驅動,做這個專案的時候就深刻地意識到,事先定義好比較好的資料結構是多麼重要。一旦資料結構定義好之後,再進行後期的資料處理,就可以很好地根據資料進行渲染了。
所以在這裡資料的後期處理就很重要,掌握好陣列的一些方法,像sort findIndex 以及split等方法都很重要。
是時候學點後端的東西啦。。。。。