菜鸟码农——差点就放弃的记账微信小程序

2,445 阅读14分钟

前言

  大家好,我是天水碧,你们可以叫我阿碧也可以叫我阿旭。是一名优秀(菜鸡)的新手码农。上个月刚刚学了一些微信小程序开发方面的知识就迫不及待地想自己写一个微信小程序——最后选择了记账类的小程序(穷人第一时间就想到了钱方面的hhh)最后由于太菜了只写了一个页面就放弃了(我很优秀的^_~)。然后学了一段时间的vue之后我突然觉得自己半途而废不好就回来继续写这个小程序了(真是幡然醒悟啊)

  经过了两天的艰苦努力(一边吃零食一边写),我终于把这个小程序的页面布局和部分功能写好啦(都说了我很菜U_U)下面请允许我给大家介绍一下我的小程序(第一个小程序也是第一次发文章,一想到有可能有大佬看到我的文章就很紧张)

项目界面和准备工作

项目页面

界面虽然一般般啦但是你们不觉得那两张背景图(我老婆^_^)很美吗

准备工作

开发必备

  1. 写代码我用的是Vscode,界面简洁使用方便.
  2. 小程序必须要有的微信开发者工具,点击就可以去下载了哦.
  3. 申请微信小程序账号(啊不然怎么写嘞)

使用到的一些神器

  1. 切图必备神器MarkMan,因为我是仿随手记的界面(当然没有人家好看)所以量各种组件图片的大小必须要用到MarkMan.
  2. 阿里巴巴矢量图标库iconFont,这个大家都知道,有很多非常可爱的小图标都可以在IconFont里面下载的.
  3. 微信开发文档,里面有很多组件和APi的使用方法,开发者必看哦.
  4. CSDN社区,当你有什么不懂的地方可以去CSDN上求助大神,妈妈再也不用担心我全是BUG啦(并没有).

开发过程

页面构造

这里只放了四个主界面哦

"pages": [
    "pages/noting/noting", //记一笔
    "pages/index/index", //主页
    "pages/account/account", //账户
    "pages/mine/mine",   //我的
  ],

首页

如图,主页分为头部结构和主体部分两个部分,其中,主体页面用了一个wx:for的功能将js中获取的数据循环出来做成一个个组件,并且在最外面嵌套了一个scroll-view的可滚动视图区域,这样当数据过多时我们就可以进行滚动查看.

<scroll-view class="records" scroll-y="{{true}}">
  <view class="recordPage" wx:for="{{records}}" wx:key="{{index}}">
    <view class="title">
      <view class="date">{{item.recordDate}}</view>
      <view class="input"></view>
    </view>
    <view class="record">
      <view class="recordIcon {{item.income == true ? 'incomeBg' : 'outcomeBg'}}">
        <image class="icon" src="{{item.recordIcon}}" />          
      </view>
      <view class="recordName">{{item.recordName}}</view>
      <view class="recordNum {{item.income == true ? 'income' : 'outcome'}}" >{{item.recordNum}}</view>
      <view class="recordNote">{{item.recordNote}}</view>
    </view>
  </view>
  </scroll-view>   

从图中和代码中我们可以看到,我在类名为recordNumrecordBg的组件都加上了三元运算符,来判断是否要加上特殊样式。我这么做的原因是,我把每一笔记录都加上了一个income的属性来判断该记录是否为支出,如果是支出记录字体图标变为绿色,收入记录则为红色,这也是记账记录最基本的功能之一. 另外,头部的总支出是通过计算支出数据之和得到的,代码如下:

 getSum () {
  let record = this.data.records;
  let sum = 0;
  for(let i = 0; i < record.length; i++) {
    sum += record[i].recordNum * 1
  }
  this.setData({
    monthSum: sum.toFixed(2)
  })
}

账户

和主页面一样,我们的账户页面也分为了两部分,主体部分同样使用了scroll-view的可滚动视图区,这是一个很简单的页面,也是我上个月唯一写的东西(好菜啊)唯一要比较注意的地方是:因为我想要显示小数点后两位的值,但是当小数点后两位的值为00时页面不予显示,所以js里面金额的data我给了一个字符串类型的数据(这样它就可以显示出0.00啦),所以当我用到这个数据来计算头部总金额时,将该数据乘1来强制转化为数据类型,具体代码如下:

getSum() {
    let type = this.data
    let sum = 0
    sum = type.cashSum * 1 + type.financeSum * 1 + type.creditorSum * 1 + type.investSum * 1 + type.virtualSum * 1;
    this.setData({
      sum
    })
  },

记一笔

这个页面是耗时最久也是最复杂的页面了(写到想放弃)

切换页

首先讲一讲它的上半部分,最上方切换功能这里是放了一个三元运算符来通过数据isActive以及设置好了的data-index判断是否加上样式tab-item-active,,代码如下:

<view class="tab-wrapper" bindtap="setActive">
    <view class="tab-item {{isActive == 0 ? 'tab-item-active' : ''}}" data-index="0">支出</view>
    <view class="tab-item {{isActive == 1 ? 'tab-item-active' : ''}}" data-index="1">收入</view>
    <view class="tab-item {{isActive == 2 ? 'tab-item-active' : ''}}" data-index="2">转账</view>
    <view class="tab-item-line" animation="{{animationData}}"></view>
  </view>

如果选择了支出或收入,就加上一个tab-item-active样式使字体颜色加深并加上下划线,此处的样式如下:

.tab-item-active{
color: #323233;
border-bottom: 2px solid #000;
}

实现该功能是因为加上了一个点击事件setActive,通过获取点击时我们所获取的index值来赋予数据isActive值,并且设置了切换时的动画效果,点击事件的函数功能代码是这样的:

setActive: function(e){
    // console.log("---",e);
    var index = e.target.dataset.index;
    // 初始化动画数据
    var animation = wx.createAnimation({
      duration: 500,
      timingFunction: 'ease-out',
      delay: 0
  });
  // 距离左边位置
  animation.left((index * 250) + 'rpx').step()
  // 设置动画
  this.setData({
      animationData: animation.export()
  });
  this.setData({
    isActive: index,
  })
  },

收入(支出的)类型选择模块

此处我们使用了一个循环语句来将数据源中已经定义好了的图标数据展示出来,并且可以点击选择某个类型的图标时改变图标的样式来表示已经被选中,并且在下方的展示面板显示其图标对应的类型名称.

这里因为要考虑到切换页面,所以我们依然利用了isActive这个数据,并且通过三元运算符来决定是不是要给页面加上display:none属性来决定页面出不出现.

添加了样式display:none;将使得div内的内容隐藏通过浏览器什么也看不见,并且隐藏的内容也不会占用空间,通常用于JS特效隐藏、隐藏统计代码显示图标
这一部分wxml代码如下:

<view class="tab-content {{isActive == 0 ? 'show' : 'hide'}}">
  <view class="layout-top">
    <view class="screen green">{{screenData}}</view>
  </view>
  <view class="outcome">
    <view class="every" wx:for="{{outcome}}" wx:key="{{index}}">
      <view class="imgWrapper" wx:for-index="{{index}}" bindtap="selectType" 
      data-index="{{item.id}}" style='{{isSelect == index ? "border:1px solid #02ad7b;":"border: none; "}}'>
        <image class="icon" src="{{item.icon}}" mode="aspectFit" bindtap="selectType">
        </image>
      </view>
      <view class="type" wx:for-index="{{index}}" bindtap="selectType" 
      data-index="{{item.id}}" style='{{isSelect == index ? "color: #02ad7b;":"border: none; "}}'>
        {{item.type}}</view>
    </view>
  </view>
</view>
<view class="tab-content {{isActive == 1 ? 'show' : 'hide'}}">
  <view class="layout-top">
    <view class="screen red">{{screenData}}</view>
  </view>
  <view class="income">
    <view class="every" wx:for="{{income}}" wx:key="{{index}}">
      <view class="imgWrapper" wx:for-index="{{index}}" bindtap="selectedType" 
      data-index="{{item.id}}" style='{{isSelected == index ? "border:1px solid #ea69a0;":"border: none; "}}'>
        <image class="icon" src="{{item.icon}}" mode="aspectFit" >
        </image>
      </view>
      <view class="type" wx:for-index="{{index}}" bindtap="selectedType" 
      data-index="{{item.id}}" style='{{isSelected == index ? "color: #ea69a0;":"border: none; "}}'>
        {{item.type}}</view>
    </view>
  </view>
</view>
<view class="tab-content {{isActive == 2 ? 'show' : 'hide'}}">
  <view class="layout-top">
    <view class="screen">{{screenData}}</view>
  </view>
</view>
<view class="display-data">
  <view class="display-type">
    <image class="display-icon" src="../../assest/icon/菜单 (1).png" style="width: 40rpx; height: 40rpx" />
    <view class="display1">{{currentType}}</view>
    <view class="display2">分类</view>        
  </view>
  <view class="display-account">
    <image class="display-icon" src="../../assest/icon/钱包 (1).png" style="width: 40rpx; height: 40rpx" /> 
    <view class="display1">{{account}}</view>
    <view class="display2">账户</view>
  </view>
  <view class="display-date">
    <image class="display-icon" src="../../assest/icon/日历.png" style="width: 40rpx; height: 40rpx" /> 
    <view class="display1">{{today}}</view>
    <view class="display2">日期</view>
  </view>
</view>

这里运用大量三元运算符来控制选择后图标的变化,点击后改变图标样式以及使被选择的图标对应类型名称出现在显示面板上,点击事件selectType代码如下:

selectType: function(e){
  var outcome = this.data.outcome;
  var index = e.target.dataset.index;
  //console.log("---",e);
    for(let i = 0; i < outcome.length; i++) {
      if (index === outcome[i].id) {
        var current = outcome[i].type;
      }
    }     
    this.setData({
      isSelect: index,
      currentType: current
    })
},

通过获取点击后的数据值data-index与自身绑定的wx:for-index使用三元运算符作比较来判断已经被选中并添加样式增加了绿色(红色)边框,如果没有这样做的话就会给每一个图标都加上样式了(血泪教训)

就像上面这个样子(后来觉得绿色背景太丑就只加一个边框),另外不知道大家有没有发现,上面每一个图标里面的图片形状都很奇怪.这并不是我没有改变图片样式,而是我在添加样式后图片并没有发生形变,细心的朋友可能看到我在上面wxml代码的image组件中加入了一个mode="aspectFit",正是因为这个属性才让我的图片恢复了正常.
原来,mode的aspectFit可以保持横纵比并且将图片完全显示出来,我也百度了一下为什么我的图片会无法横向形变然后找到了这个方法解决了问题,如果你以后碰到了这个问题不妨一试哦^_+ 这里的wxss样式如下

.tab-wrapper{
  position: absolute;
  background: rgba(red, green, blue, 0);
  width: 360rpx;
  border-top: 1rpx solid rgb(246, 246, 246);
  border-bottom: 1rpx solid #F6F6F6;
  display: flex;
  margin-left: 50rpx;
}
.tab-item{
  display: inline-block;
  flex: 1;
  font-size:36rpx;
  text-align: center;
  background: #fff;
  color: #999;
  padding: 12rpx 0;
}
.tab-item-active{
  color: #323233;
  border-bottom: 2px solid #000;
}
.show{
  display: block;
}
.hide{
  display: none;
}
.tab-content{
  position: absolute;
  width: 100%;
  margin-top: 215rpx;
  height: 225rpx;
}
.outcome {
  position: absolute;
  width: 100%;
  height: 590rpx;
  margin-top: 0;
  line-height: 50rpx;
  margin: 23rpx 23rpx 0 23rpx;
}
.income {
  position: absolute;
  width: 100%;
  height: 590rpx;
  margin-top: 0;
  line-height: 50rpx;
  margin: 23rpx 23rpx 0 23rpx;
}
.every {
  width: 94rpx;
  height: 126rpx;
  float: left;
  margin: 23rpx;
}
.imgWrapper {
  width: 94rpx;
  height: 94rpx;
  border-radius: 50%;
  background: rgb(238, 237, 237);
}

.icon {
  text-align: center;
  height: 80rpx;
  width: 80rpx;
}
.type {
  font-size: 11px;
}
/* 显示选择面板 */
.display-data {
  height: 72rpx;
  width: 100%;
  position: absolute;
  margin-top: 790rpx;
}
.display-type {
  flex: 1;
  float: left;
  background: rgb(204, 203, 203);
  width: 250rpx;
  height: 100%;
}
.display-account {
  flex: 1;
  float: left;
  background: rgb(160, 159, 159);
  width: 250rpx;
  height: 100%;
}
.display-date {
  flex: 1;
  float: left;
  background: rgb(124, 124, 124);
  width: 250rpx;
  height: 100%;
}
.display-icon {
  float: left;
  margin-left: 22rpx;
  margin-top: 11rpx;
}
.display1 {
  font-size: 26rpx;
  color: #fff;
}
.display2 {
  font-size: 20rpx;
  color: rgb(0, 0, 0);
  font-weight: bold;
  margin-left: 65rpx;
  float: left;
}

然后就是最下方的计算版面和上方的显示输入内容版面了,计算面板的wxml代码如下:

view class="content">
    <!-- 计算版面 -->
    <view class="layout-bottom">
      <view class="btnGroup">
        <view class="item blue" bindtap="clickBtn" id="{{id9}}">9</view>
        <view class="item blue" bindtap="clickBtn" id="{{id8}}">8</view>
        <view class="item blue" bindtap="clickBtn" id="{{id7}}">7</view>
        <view class="item orange" bindtap="clickBtn" id="{{idj}}">-</view>
      </view>
      <view class="btnGroup">
        <view class="item blue" bindtap="clickBtn" id="{{id6}}">6</view>
        <view class="item blue" bindtap="clickBtn" id="{{id5}}">5</view>
        <view class="item blue" bindtap="clickBtn" id="{{id4}}">4</view>
        <view class="item orange" bindtap="clickBtn" id="{{idadd}}">+</view>
      </view>
      <view class="btnGroup">
        <view class="item blue" bindtap="clickBtn" id="{{id3}}">3</view>
        <view class="item blue" bindtap="clickBtn" id="{{id2}}">2</view>
        <view class="item blue" bindtap="clickBtn" id="{{id1}}">1</view>
        <view class="item orange" bindtap="clickBtn" id="{{ide}}">=</view>
      </view>
      <view class="btnGroup">
        <view class="item blue " bindtap="clickBtn" id="{{idc}}">C</view>
        <view class="item blue " bindtap="clickBtn" id="{{id0}}">0</view>
        <view class="item blue" bindtap="clickBtn" id="{{idd}}">.</view>
        <view class="item orange" bindtap="save" id="{{ids}}">保存</view>
      </view>
    </view>
  </view>

为了实现输入的效果,我在js里面为每一个按钮都定义的相对应的数据

idb:"back",
 idc:"clear",
 idt:"toggle",
 idadd:"+",
 id9:"9",
 id8:"8",
 id7:"7",
 idj:"-",
 id6:"6",
 id5:"5",
 id4:"4",
 idx:"×",
 id3:"3",
 id2:"2",
 id1:"1",
 id0:"0",
 idd:".",
 ide:"=",
 ids:"保存",
 screenData:"0.00",
 operaSymbo:{"+":"+","-":"-",".":"."},
 lastIsOperaSymbo:false,
 iconType:'waiting_circle',
 iconColor:'white',
 arr:[],
 logs:[],
 ```
 当我们点击按钮时,会执行相应的功能,并且会有相应的样式变化
 wxss代码如下
 ```
 /* 计算版面 */
.content {
height: 420rpx;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
font-family: "Microsoft YaHei";
overflow-x: hidden;
bottom: 0;
position: fixed;
background: rgb(192, 191, 191);
}
.layout-top{
position: absolute;
/* float: left; */
margin-top: -115rpx;
height: 125rpx;
border-bottom: 1px solid rgb(119, 119, 119);
width: 100%;
}
.layout-bottom{
position: fixed;
bottom: 0;
width: 100%;
}
.screen {
text-align: left;
width: 100%;
/* line-height: 200rpx; */
padding: 0 10rpx;
font-weight: bold;
font-size: 40px;
}
.green {
color: green
}
.red {
color: red
}
.btnGroup {
display: flex;
flex-direction: row;
flex: 1;
width: 100%;
height: 100rpx;
background-color: #fff;
}
.item {
width:25%;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
margin-top: 1px;
margin-right: 1px;
}
.item:active {
background-color: #ff0000;
}
.zero{
width: 50%;
}
.orange {
color: #fef4e9;
background: #f78d1d;
font-weight: bold;
}
.blue {
color:#d9eef7;
background-color: #0095cd;
}
.iconBtn{
display: flex;
}
.icon{
display: flex;
align-items: center;
width:100%;
justify-content: center;
}

以及计算版面的js代码:

clickBtn:function(event){
    var id = event.target.id;
    if(id == this.data.idb){  //退格←
      var data = this.data.screenData;
      if(data == "0"){
          return;
      }
      data = data.substring(0,data.length-1);
      if(data == "" || data == "-"){
          data = 0;
      }
      this.setData({"screenData":data});
      this.data.arr.pop();
    }else if(id == this.data.idc){  //清屏C
      this.setData({"screenData":"0"});
      this.data.arr.length = 0;
    }else if(id == this.data.idt){  //正负号+/-
      var data = this.data.screenData;
      if(data == "0"){
          return;
      }
      var firstWord = data.charAt(0);
      if(data == "-"){
        data = data.substr(1);
        this.data.arr.shift();
      }else{
        data = "-" + data;
        this.data.arr.unshift("-");
      }
      this.setData({"screenData":data});
    }else if(id == this.data.ide){  //等于=
      var data = this.data.screenData;
      if(data == "0"){
          return;
      }
      //eval是js中window的一个方法,而微信页面的脚本逻辑在是在JsCore中运行,JsCore是一个没有窗口对象的环境,所以不能再脚本中使用window,也无法在脚本中操作组件                 
      //var result = eval(newData);           
      
      var lastWord = data.charAt(data.length);
      if(isNaN(lastWord)){
        return;
      }

      var num = "";

      var lastOperator = "";
      var arr = this.data.arr;
      var optarr = [];
      for(var i in arr){
        if(isNaN(arr[i]) == false || arr[i] == this.data.idd || arr[i] == this.data.idt){
          num += arr[i];
        }else{
          lastOperator = arr[i];
          optarr.push(num);
          optarr.push(arr[i]);
          num = "";
        }
      }
      optarr.push(Number(num));
      var result = Number(optarr[0])*1.0;
      console.log(result);
      for(var i=1; i<optarr.length; i++){
        if(isNaN(optarr[i])){
            if(optarr[1] == this.data.idadd){
                result += Number(optarr[i + 1]);
            }else if(optarr[1] == this.data.idj){
                result -= Number(optarr[i + 1]);
            }else if(optarr[1] == this.data.idx){
                result *= Number(optarr[i + 1]);
            }else if(optarr[1] == this.data.iddiv){
                result /= Number(optarr[i + 1]);
            }
        }
      }
       this.setData({"screenData":result+""});
    }else{
      if(this.data.operaSymbo[id]){ //如果是符号+-*/
        if(this.data.lastIsOperaSymbo || this.data.screenData == "0"){
          return;
        }
      }

      var sd = this.data.screenData;
      var data;
      if(sd == 0){
        data = id;
      }else{
        data = sd + id;
      }
      this.setData({"screenData":data});
      this.data.arr.push(id);

      if(this.data.operaSymbo[id]){
        this.setData({"lastIsOperaSymbo":true});
      }else{
        this.setData({"lastIsOperaSymbo":false});
      }
    }
  },

这里我使用了手记里提供的微信小程序计算机的相应代码,感谢亲爱的作者Dunizb

计算面板运算效果如下:

进行加法运算:


减法运算如下:

清屏功能如下:

至于保存功能,emmmmm由于涉及到与其他页面数据联通并展示我暂时还没研究透彻就还没有完成(我果然好菜...),不过我一定会研究透彻的!!!

我的

写这个页面我真的好开心因为这个页面真的是最简单的一个页面!!! 它是长这个样子的:

代码很简单就不展示出来啦

结语

其实我完全是怀着不安的心写完这篇文章的,因为我也知道我的代码还有很多需要改进的地方,我也很害怕会有人说我写得很差,更怕自己辛辛苦苦写的东西无人问津.

但是我现在更多的是释然,自己终于迈出第一步了不是吗?就算没有人认同我还是自己成长了一些啊,自我认同才是最重要的不是吗?

如果你看完了我的这篇文章的话,请接收我最真挚的感谢,毕竟小菜鸟也是需要你的鼓励呀

最后,附上我这个小程序的github地址,这个小程序并不完善,但是我很希望它能够让一些人看到甚至是帮助到一些人.我是菜鸟,但没有人会永远是菜鸟,加油!!!