微信小程序-记账本

    技术2022-07-11  76

    版权声明:本文为博主「张金城大呵呵」的原创文章,遵循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解决。

    对于前端开发而言,微信小程序因为其简单快速、开发成本低、用户流量巨大等特点,逐渐成为了前端开发必会的一个技能。

     

    Processed: 0.016, SQL: 9