版权声明:本文为博主「张金城大呵呵」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_46008509/article/details/107071988
项目说明
这是一款具有记账功能的小程序-记账本,简单分为四个模块:首页、记账、报表、我的。首页页面初始数据显示当月的收入支出和结余的情况、以及当日收支,有列表可以选择其他日期的记账信息。记账页面有记账类型、消费类型、支付类型和记账信息可选择,记账信息选择记账时间、金额和备注,最后选择保存。报表模块页面有年列表、月列表和日列表选择记账的日期,选择支出或收入会分别显示当年的收支总额,以及显示百分比饼图可直观看到年度的消费类型比例。最后是我的模块,提供授权登录来显示用户的头像及昵称。
模块概览
详细设计
首页
分为本月结余、本月支出、本月收入,以及本日支出收入和详情列表,还有时间选择可以选择其他日期的详细收入支出。
代码分析:
.fl{
float:left;
}
.fr{
float:right;
}
.clearfix::after{
content: "";
display: block;
clear: both;
}
向左向右浮,,浮动会脱离文档流,这时候父元素没有高度的话父元素高度不为0,所以需要清除浮动影响。
data: {
dateRange: {
start: "",
end: ""
},
msgData: [],
isFristShow: true,
currentDate: "",
isToday: true,
cost: {
shouru: 0,
zhichu:0
},
monthDate: "", /
monthCost: {
shouru: 0,
zhichu: 0
},
dateRange为日期范围数据,msgData: []是获取的记账记录信息,isFristShow: true用来判断是否页面首次加载,currentDate表选中的日期,isToday: true, 判断选中日期是否是当天,cost是选中日期的收入与支出总额,初始值0,monthDate、monthCost是选中的月份以及该月的收入与支出。
在获取时间范围函数setDate里设置开始日期和结束日期
实例化var timer = new Date();获取年var year = timer.getFullYear();获取月份, 因为月份从0开始,真实月份需要加一 var month = timer.getMonth() + 1; 获取日var date = timer.getDate();
接着设置开始日期和结束日期var start = year - 1 + "-" + this.addZero(month) + "-" + this.addZero(date); var end = year + "-" + this.addZero(month) + "-" + this.addZero(date);
最后调用这些方法设置data里的值,
this.setData({
dateRange: {
start,
end
}
写一个更改日期时间函数selectDate
先重新获取数据this.getMsgData(e.detail.value); 获取选择的月份,如果选择的月份和上一次选择的月份相同,则不执行getMonMsgData
if (e.detail.value.substring(0, 7) != this.data.monthDate){
this.data.monthDate = e.detail.value.substring(0, 7)
this.getMonMsgData(this.data.monthDate)
}
getMsgData:函数是获取某天记账记录信息
先let that = this; 将收入支出累加前清零 that.data.cost.zhichu = 0;
that.data.cost.shouru = 0;
调用get_msg_data 云函数
wx.cloud.callFunction({
name: "get_msg_data",
data: {
date: time
},
success: function(res){
wx.hideLoading();
console.log("调用get_msg_data 云函数成功-->",res)
let data = res.result.data;
data.forEach(v =>{
if(v.costType == "zhichu"){
that.data.cost.zhichu += Number(v.money)
}else{
that.data.cost.shouru += Number(v.money)
}
也可简写为 that.data.cost[v.costType] += Number(v.money);
v.money = Number(v.money).toFixed(2);保留小数位,调用对象只能是数字,Number() 强制转换为数字类型,然后将收入与支出转千分位 toLocaleString(),只能是数字类型调用。
that.data.cost.zhichu = that.data.cost.zhichu.toLocaleString();
that.data.cost.shouru = that.data.cost.shouru.toLocaleString();
接着判断选择的日期是否是当天
if(time == that.data.dateRange.end){
that.data.isToday = true;
}else{
that.data.isToday = false;
}
修改currentDate,let timeArr = time.split("-");
获取今年月份,let year = new Date().getFullYear();
if(year == timeArr[0]){
that.data.currentDate = timeArr[1] + "月" + timeArr[2] + "日";
}else{
that.data.currentDate = timeArr[0] + "年" +timeArr[1] + "月" + timeArr[2] + "日";
}
因为获取对应月的记账记录信息,写一个函数getMonMsgData:
数据清零,然后调用get_msg_data云函数,获取对应月的记账记录信息 再遍历。 data.forEach(v =>{
that.data.monthCost[v.costType] += Number(v.money);
})
本月结余就等于 let surplus = (that.data.monthCost.shouru - that.data.monthCost.zhichu).toFixed(2).split(".");
console.log("surplus-->", surplus)
将支出收入保留小数位 that.data.monthCost.zhichu = that.data.monthCost.zhichu.toFixed(2);
that.data.monthCost.shouru = that.data.monthCost.shouru.toFixed(2);最后页面响应setdata
报表
布局:
头部是导航栏,有年份的年列表,月份的月列表,筛选则是日列表。
下面绑定的是收入支出的总和,最后是百分比展示消费类型比例的饼图。
设置页面的初始数据
data: {
isYear: false, 表是否显示年列表
isDate: false, 表示是否显示日列表
allMsgData: [], 为当前用户所有的记账记录,主要为获取导航栏的时间
currentYear: "", 是当前选中年份
yearList: [], 表示年份列表
monthList: [], 表示月份列表
dateList: [], 表示日列表
tabData: [
{
title: "年收入",
money: 0,
type: "shouru",
isAct: true
},
{
title: "年支出",
money: 0,
type: "zhichu",
isAct: false
}
],
tyData:{}, 用来根据收入支出分好类的数据
isCanShow: true,
screenWidth:0 屏幕占比
点击显示列表函数showItem:
接收参数let type = e.currentTarget.dataset.type
判断有没有选择月份,存在isAct为true,则是月份已激活
for(var i = 0; i < this.data.monthList.length; i++){
if (this.data.monthList[i].isAct){
active = true;
}
active = false,说明月份没有激活的,isDate不需要设置,终止代码
if(!active){
wx.showToast({
title: '请选择月份',
icon: 'none',
duration: 2000,
mask: true
})
return;
}
getAllMsgData函数获取所有数据,处理时间导航
调用云函数get_msg_data, 获取所有数据之后遍历数据,获取年份,需满足条件截取的年份在yearList不存在,indexOf(要查询的元素/字符),数组和字符串都可调用的方法,若数组存在当前查询的元素,则返回元素首次出现的下标,若不存在则返回 -1,yearList不存在该年份的时候添加该年
res.result.data.forEach(v =>{
let y = v.date.substring(0,4)
if (that.data.yearList.indexOf(y) == -1) {
that.data.yearList.push(y)
}
})
升序排列,从小到大,数组排序 sort(fn) 参数接收一个函数,会直接影响原函数that.data.yearList.sort(function (a, b) { return a - b })
selectDate:function(time,start,end,dataName){
time: 如果是获取月份则传年份进来,如 2020 ,如果是获取日则传年yue进来,如 2020-04
start: 开始截取下标,如果是获取月份,从下标为5开始截取 如果是获取日份,从下标为8开始截取
end: 结束截取下标,如果是获取月份,从下标为57结束截取 如果是获取日份,从下标为10结束截取
dataName: 要修改的数据名称 如果是获取月份是修改monthList 如果是获取日份则修改dateList
allMsgData.forEach(v =>{
let mon = Number(v.date.substring(start, end));
if(v.date.indexOf(time) != -1 && dateArr.indexOf(mon) == -1){
dateArr.push(mon)
}
})这里要满足两个条件:1. 要是当前的年份的月份2.在dataArr这个数组里不存在。
然后是图表的绘制函数,需要先引入图表charts插件
var wxCharts = require('../../js/wxcharts-min.js');
drawPie: function (series){
new wxCharts({
canvasId: 'pieCanvas',
type: 'pie', 图表类型为饼图
series,
width: this.data.screenWidth,
height: 300,
dataLabel: true
});页面上canvas组件的id,因需要获取id,所以需要在页面渲染完成后才执行
记账页面
布局:
内容区里分为记账类型的切换(支出或收入),消费类型的轮播图标和标题,中间是账户选择即用户的支付类型选择,最后下方是记账信息,有记账的时间、账目的金额和其他备注信息。
最后还有保存按钮保存信息。
data: {
bookkeepingData: []
typeData: [
{
title: "支出",
isActive: true,
type: "zhichu"
},
{
title: "收入",
isActive: false,
type: "shouru"
}
],
accountData:[
{
"title": "现金",
"isActive": true,
"type": "xianjin"
},
{
"title": "微信钱包",
"isActive": false,
"type": "wechat"
},
{
"title": "支付宝",
"isActive": false,
"type": "zhifubao"
} ,
{
"title": "储蓄卡",
"isActive": false,
"type": "chuxuka"
},
{
"title": "信用卡",
"isActive": false,
"type": "xinyongka"
}
],
dateRange: {
start: "",
end: ""
},
info: {
date: "",
money: "",
comment: ""
},
isAuth:false
},
bookkeepingData: []记账类型,typeData消费类型, accountData支付类型,dateRange日期范围数据,info里是用户输入的时间、金额、备注信息。。
切换标签函数:
获取数据data里的值
let name = e.currentTarget.dataset.name;
let data = this.data[name];
if(e.currentTarget.dataset.active){
console.log("当前已激活")
return;
}如果事件对象active为真则是激活状态,直接返回。
for(let i = 0 ; i < data.length; i++){
if(data[i].isActive){
data[i].isActive = false;
break;
}将数据中原先的isActive=true 设置为false,即取消激活状态
let index = e.currentTarget.dataset.index; 获取当前点击的数据下标
data[index].isActive = true;data里找到第index项设置其激活状态
this.setData({
[name]: data
})最后设置data里的值,把要更改的变量传进来。
<view class="banner">
<swiper class="banner-swiper" indicator-dots="true" indicator-active-color="#FEDB5A" >
<block wx:for="{{bookkeepingData}}" wx:key="index">
<swiper-item>
<view class="swiper-item">
<view class="item {{itemType.isAct ? 'active' : ''}}" wx:for="{{item}}" wx:for-item="itemType" wx:for-index="id" wx:key="id" bindtap="selectType" data-index="{{index}}" data-id="{{id}}">
<view class="icon">
<image src="{{itemType.icon_url}}"></image>
</view>
<view class="text">{{itemType.title}}</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
以上代码为轮播消费类型的图标和标题。
获取记账类型函数getTypeData:
先设置一个加载框 wx.showLoading({
title: '加载中'
})
调用云函数 get_type_data ,获取记账类型数据
wx.cloud.callFunction({
name: "get_type_data",
success: function(res){
然后关闭加载框wx.hideLoading(),
获取返回来的数据,let data = res.result.data,然后添加字段 ,用来提示是否被选中的作用;
data.forEach(v =>{
// console.log(v)
v.isAct = false;
})
let type = [];用来存放处理好的数据 let begin = 0;设置开始截取下标
while(begin < data.length){
let tmp = data.slice(begin,begin+8);
begin += 8;
type.push(tmp);
} 这里为While语句 先判断条件是否成立,假如成立则执行{}里的代码块,执行完会再次判断条件,如果条件再次成立,代码块会再次执行,直到条件不成立。其中slice(开始截取的下标,结束截取的下标) 截取的部分不包括结束截取的下标对应的内容,返回一个新的数组,不会影响数组。 然后设置bookkeepingData 对应var that = this;
that.setData({
bookkeepingData: type
})
记账类型的点击切换函数:selectTYpe
首先获取bookkeepingData的数据:let bannerType = this.data.bookkeepingData;
获取第一层索引let index = e.currentTarget.dataset.index; 获取第二层索引let id = e.currentTarget.dataset.id; 然后判断当前点击的盒子是否为激活状态,如果是激活状态则取消选择,即取消激活状态 。 激活前,把上一个isAct改为fasle。找到激活的isAct就直接终止循环
if(bannerType[index][id].isAct){
bannerType[index][id].isAct = false;
}else{
for (var i = 0; i < bannerType.length; i++) {
for (var j = 0; j < bannerType[i].length; j++) {
if (bannerType[i][j].isAct) {
bannerType[i][j].isAct = false;
break;
}
}
}
还需要设置当前点击类型盒子的isAct,激活状态bannerType [index] [id]. isAct = true;
页面响应要setData
this.setData({
bookkeepingData:bannerType
})
},
getInfo函数:获取用户填写的时间,金额,备注信息
首先获取当前修改的info字段名let title = e.currentTarget.dataset.type;
this.data.info[title] = e.detail.value; 如果数据需要页面响应,则需用setData修改 this.setData({
info: this.data.info
})
原系统时间一般为2020年x月x日,即2020-5-20为了使日期格式完整写一个补零函数 addZero: function(num){
return num < 10 ? "0" + num : num;
},如果num值小于10则补0在前面,如2020-5-10补零为2020-05-10
获取时间范围函数setDate设置开始日期和结束日期
实例化对象var timer = new Date();
获取年份var year = timer.getFullYear();获取月份, 月份从0开始,真实月份需要加一var month = timer.getMonth() + 1; 获取日var date = timer.getDate();设置开始日期var start = year - 1 + "-" + this.addZero(month) + "-" + this.addZero(date);设置结束日期var end = year + "-" + this.addZero(month) + "-" + this.addZero(date); 接着页面响应设置data里日期的值
this.setData({
dateRange: {
start,
end
},
"info.date": end
})
添加记账信息到数据库的函数addMsgData:
首先设置存储记账信息的数据。let data = {};然后用for循环获取消费类型是收入还是支出,找到已选择的数据就终止循环
for(var i = 0; i < this.data.typeData.length; i++){
if (this.data.typeData[i].isActive){
data.cost = this.data.typeData[i].title;
data.costType = this.data.typeData[i].type;
break;
}
}
接着我们要获取激活的记账类型,先判断记账类型是否有哦被选中,默认是否let isSelected = false;依然是使用for循环获取,找到后break退出循环
for(var i = 0; i < this.data.bookkeepingData.length; i++){
for (var j = 0; j < this.data.bookkeepingData[i].length; j++){
if (this.data.bookkeepingData[i][j].isAct){
data.iconId = this.data.bookkeepingData[i][j]._id;
data.icon = this.data.bookkeepingData[i][j].icon_url;
data.title = this.data.bookkeepingData[i][j].title;
data.iconType = this.data.bookkeepingData[i][j].type;
isSelected = true;
break;
}
}
}
如果记账类型没有被选中,则弹出提示框提醒用户去选择记账类型。
if (!isSelected){
wx.showToast({
title: '请选择记账类型',
icon: 'none',
duration: 2000,
mask: true
})没有选中则return终止代码,下面函数不执行,数据不能提交
获取账户选择,即用户的支付选择类型accounttype
for(var i = 0; i < this.data.accountData.length; i++){
if(this.data.accountData[i].isActive){
data.account = this.data.accountData[i].title;
data.accountType = this.data.accountData[i].type;
}
}同样的 如果用户没有在记账信息里填写信息则弹出显示消息提示框 if(this.data.info.money == ""){
wx.showToast({
title: '请输入金额',
icon: 'none',
duration: 2000,
mask: true
})
获取用户填写的时间和金额备注信息,
for(var key in this.data.info){
data[key] = this.data.info[key]
}在这里,info是对象不能用for循环,要用for in 或for Each ,key是 对象的键名,in后是需要遍历的对象或数组
添加信息的月份是data.month = this.data.info.date.substring(0,7);
提示保存信息 wx.showLoading({
title: '正在保存'
})
记录一下this,let that = this;接着调用云函数add_msg_data
wx.cloud.callFunction({
name: "add_msg_data",
data,
success: function(res){
wx.hideLoading()
wx.showToast({
title: '保存成功',
icon: 'none',
duration: 2000,
mask: true
})
console.log("调用云函数add_msg_data成功-->",res)
that.resetData()
},
fail: function(err){
wx.hideLoading()
console.log("调用云函数add_msg_data失败-->", err)
}
})这里that.resetData()是为了重置页面,恢复原样
ResetData函数是为了重置数据
重置消费类型 this.data.typeData[0].isActive = true;
this.data.typeData[1].isActive = false;
重置记账类型 找到激活的类型,就取消激活
for (var i = 0; i < this.data.bookkeepingData.length; i++) {
for (var j = 0; j < this.data.bookkeepingData[i].length; j++) {
if (this.data.bookkeepingData[i][j].isAct) {
this.data.bookkeepingData[i][j].isAct = false;
break;
}
}
}
重置账户选择 this.data.accountData[0].isActive = true;
for(var i = 1; i < this.data.accountData.length; i++){
this.data.accountData[i].isActive = false;
}
然后页面响应,讲数据设回当天日期
this.setData({
typeData: this.data.typeData,
bookkeepingData: this.data.bookkeepingData,
accountData: this.data.accountData,
info: {
date: this.data.dateRange.end,
money: "",
comment: ""
}
})
Onshow函数是生命周期回调—监听页面显示,是页面显示/切入前台时触发
在这里设置一个获取用户是否授权,若res.authSetting["scope.userInfo"] 为 true 则已授权
let that = this;
wx.getSetting({
success: function (res) {
console.log(res.authSetting["scope.userInfo"])
if (res.authSetting["scope.userInfo"]) {
that.setData({
isAuth: true
})
}
设置一个点击获取授权框,授权成功返回用户数据
onGetUserInfo: function(e){
if (e.detail.userInfo) {
this.setData({
isAuth: true
})
}
}
我的模块:
未授权状态
授权获得用户信息,用户名和用户头像
六、实训总结、体会,不足及经验教训:
虽然经常手机上用到微信小程序,但开发简单的微信小程序却是我的第一次。第一节课,先是申请个账号,按流程注册完成之后就会有一个APPID(小程序ID),这个ID很重要,在IDE创建项目和项目上线都是需要的。同时我发现,可能由于管理需求,个人开发那虽然一个人名下可以有五个小程序,但一个邮箱只能对应管理一个小程序,如果你想开发多几个还得多用几个邮箱,这不坑嘛?谁会好几个邮箱呢,实在麻烦。申请账号后,就是安装开发者工具IDE,按老师的要求下载了稳定版Stable Build。下载好,发觉界面还行,但是拓展功能好像一般, 也不支持啥快捷键。然后我也对小程序开发有了一点基本认识,认识了这有四种文件类型json、js、wxml、wxss,json和js还算熟悉,实际上wxml就是html,而wxss就是写样式的css。然后wxml也有类似vue的那种{{}}绑定数据的方法。然后我也发现是这里需要要用var that=this的定义去用this。
开发过程中遇到的问题也不少,首先比较重要一个是直接修改this.data而补调用this.setData是无法改变页面的状态的,还会造成数据不一致,即修改页面的data只能通过setData。列表条目多时容易出现卡顿,要使用wx:key解决。
对于前端开发而言,微信小程序因为其简单快速、开发成本低、用户流量巨大等特点,逐渐成为了前端开发必会的一个技能。