原创:豪豪

前言

最近沉迷小程序开发,发现了一款功能、界面、体验俱佳的小程序“旅行小账本”。着手做了个简约版——"旅行小账本"。效果比较满意,毕竟前后台一人单干。

IDE

  • 微信开发者工具
  • VSCode

小程序开发必然少不了微信开发者工具,再加上其对云开发的全面支持,再好不过的开发利器。但熟悉微信开发者工具的朋友们应该知道,它不支持Emmet缩写语法,并且wxml的属性值默认用单引号表示(强迫症表示很难受)。 而VSCode很好的补足了微信开发者工具的不足之处,并且支持多元化插件开发,轻量好用。 所以这里推荐采用微信开发者工具+VSCode配合开发。微信开发者工具负责调试、模拟小程序运行情况,VSCode负责代码编辑工作。二者各司其职,会使开发更加的高效、便捷。

总体架构

该项目基于小程序云开发,使用的模板是云开发快速启动模板。 由于是个全栈项目,前端使用小程序所支持的wxml + wxss + js开发模式,命名采用BEM命名规范。后台则是借助云数据库+云储存进行数据管理。

项目总体结构

|-travelbook  项目名|-cloudfunctions  云函数模块|-deleteItems 级联删除--云函数|-getTime     获取时间--云函数|-miniprogram  项目模块|-components  自定义组件|-accountCover  账本封面组件|-spendDetail   支出细节组件|-pages  页面|-accountBooks     总账本页|-accountCalendar  账本日历页|-accountDetail    支出细节页|-accountList      支出明细页|-accountPage      选定账本页|-editAccount      账本编辑页|-index            首页|-vant-weapp   有赞vant框架组件库|-···      系列组件...app.js         全局jsapp.json       全局json配置app.wxss       全局wxss

逆向工程

在做该小程序之前,有必要进行项目的逆向工程,进一步解构每一个页面,从而深入了解这款小程序的交互细节。那么现在我假设自己为腾讯旅游的产品设计师,在绘制完界面原型后,撰写了相应的交互文档。当然解构过程中可能有些细节处理并没有那么仔细到位...

以下是我绘制的界面原型

接下来对每个页面的细节进行解构,并完成简单的wxml结构

<!--switchList使用定位布局-->
<view bindtap="switchList" class="list"></view><!--newAccount使用flex布局-->
<view class="newAccount" bindtap="createNewAccount"><view class="desc">旅行中的每一笔开支都有独特的意义!</view><image src="{{}}"></image><view class="title">创建一个新账本</view>
</view>

<!--整体用flex + 百分比布局-->
<input type="text" class="accuntName" placeholder="旅行账本名称" bindinput="getInput" /><van-panel title="选择封面" class="panel"><van-row class="imageBox"><!--使用wx:for遍历数据库账本图片信息--><van-col span="8" class="imgCol" bindtap="selectThis"><image class="select" src="{{}}"></image></van-col><van-col span="8"><view class="addBox" bindtap="useMore">更多封面</view></van-col></van-row>
</van-panel><button type="primary" bindtap="save">保存</button>
<button type="warn" bindtap="delete">删除</button>

<view class="accountDesc" bindtap="viewDetail"><!--使用wx:for遍历数据库账本信息--><view class="accountName"><view>{{}}</view><view class="accountTime">{{}}</view></view><!--绝对定位--><image class="updateImg" catchtap="editAccount" src="{{}}"></image>
</view>

<!--switchList使用定位布局-->
<view bindtap="switchList" class="list"></view><view class="account__list-year">{{}}</view>
<view class="account__list-new account__list-public" bindtap="createNewAccount"><!--日期小圆点--><view class="account__list-point"></view><view class="account__list-time">{{}}</view><image src="{{}}"></image><view class="account__list-title">创建一个新账本</view>
</view><!--使用wx:for遍历数据库账本信息-->
<view class="account__list-item account__list-public" bindtap="viewDetail"><!--日期小圆点--><view class="account__list-point"></view><image src="{{}}" mode="aspectFill"></image><view class="account__list-name">{{}}</view><view class="account__list-time">{{}}</view><image class="account__list-update" catchtap="editAccount" src="{{}}"></image></view>

<view class="account__spend"><image bindtap="getCalendar" class="account__spend-calendar" src="{{}}"></image><view class="account__spend-text"><view class="account__spend-total">总花费(元)</view><view class="account__spend-num">{{}}</view></view><image bindtap="accountAnalyze" class="account__spend-detail" src="{{}}"></image>
</view><view class="account__show-time">今天</view><view class="account__show-detail"><view class="account__show-income account__show-public"><view class="account__show-title">收入(元)</view><text class="account__show-in">+{{}}</text></view><view class="account__show-spend account__show-public"><view class="account__show-title">支出(元)</view><text class="account__show-out">-{{}}</text></view>
</view><!--使用wx:for遍历数据库账本信息-->
<view class="account__show-items-spend"><view><image src="{{}}"></image></view><text>{{}}</text><text class="account__show-items-money">{{}}</text>
</view>

<!--日历使用极点日历的插件-->
<!--json中做配置-->
"usingComponents": {"calendar": "plugin://calendar/calendar"
}<!--js改变样式-->
days_style.push({month: 'current',day: new Date().getDate(),color: 'white',background: '#e0a58e'
})<!--wxml中引用-->
<calendar weeks-type="cn" cell-size="50" next="{{true}}" prev="{{true}}"show-more-days="{{true}}" calendar-style="demo6-calendar"header-style="calendar-header"board-style="calendar-board" active-type="rounded"lunar="true" header-style="header"calendar-style="calendar"days-color="{{days_style}}">
</calendar>

<!--顶栏日期及收支结构-->
<view class="account__title"><text class="account__title-time">{{}}</text><text class="account__title-spend">支出{{}}元 收入{{}}元</text>
</view><!--收支细节结构 使用flex弹性布局-->
<view class="account__detail"><image src="{{}}"></image><view class="account__detail-name">{{}}</view><view class="account__detail-money">{{}}</view>
</view>

<!--使用vant框架的van-tabs组件-->
<!--并封装自定义组件复用收支页,自定义组件后面会详细说明-->
<van-tabs active="{{ active }}" bind:change="onChange"><van-tab title="支出"><spendDetail detail="{{detail}}" accountKey="{{accountKey}}"></spendDetail></van-tab><van-tab title="收入"><spendDetail detail="{{income}}" accountKey="{{accountKey}}"></spendDetail></van-tab>
</van-tabs>

云开发

在做完逆向工程的解构,页面基础结构基本搭建完成。但页面依旧是静态的,需要数据来填充。所以第二步就是数据库的设计。而小程序的云控制台恰好提供了数据的操作功能,为数据驱动提供基石。

云数据库设计

云数据库是一种NoSQL数据库。每一张表是一个集合。值得注意的是在设计数据库时,_id_openid这两个字段需要带上。_id是表的主键,而_openid是用户标识,每个用户都有不同的_openid,可区分不同用户。

以下是项目中的数据表设计

cover_photos 账本封面表  用于存储创建账本时需要的封面信息- _id- _openid- cover_index 封面索引- cover_url   封面url- isSelected  封面是否选中
accounts 账本表   用于存储用户创建的账本- _id- _openid- accountKey  账本唯一标识- coverUrl    账本封面- i           账本索引- inputValue  账本名字- now         账本创建时间- spend       账本总花费
account_detail 支出类型表   用于存储消费类型- _id- _openid- detail       类型细节- pic_index    消费类型索引- pic_url      未点击时的图片- pic_url_act  点击后的图片- type         消费类型
account_income 收入类型表   用于存储收入类型- _id- _openid- pic_index    收入类型索引- pic_url      未点击时的图片- pic_url_act  点击后的图片- type         收入类型
spend_items   消费明细表- _id- _openid- accountKey   账本唯一标识- address      消费地点- desc         消费描述- fullDate     消费时间- money        消费金额- pic_type     消费类型- pic_url      消费类型图片

云储存管理

这是个非常实用的板块。类似于百度云盘,它提供了文件存储、上传与下载功能。

除此之外,它还会将你所上传的资源自动进行压缩操作,并生成一个地址供你引用。该项目中的一些图片资源就是存在于此,然后在云数据库的字段中引用这些资源地址即可,十分方便,不必在本地存储,占用小程序内存。

云函数设计

云函数简单来说就是在云后端(Node.js)运行的代码,本地看不到这些代码的执行过程,全封闭式只暴露接口供本地调用执行,本地只需等待云端代码执行完毕后返回结果。这也是面向接口编程的思想体现。

项目中的云函数设计

// getTime  获取当前时间并格式化为 yyyy-mm-dd// 云函数入口文件
const cloud = require('wx-server-sdk')// 初始化云函数
cloud.init()// 云函数入口函数
exports.main = async (event, context) => {var date = new Date()var seperator1 = "-"var year = date.getFullYear()var month = date.getMonth() + 1var strDate = date.getDate()if (month >= 1 && month <= 9) {month = "0" + month}if (strDate >= 0 && strDate <= 9) {strDate = "0" + strDate}// 格式化当前时间var currentdate = year + seperator1 + month + seperator1 + strDatereturn currentdate
}
// deleteItems  批量删除,云数据库的批量删除只允许在云函数中执行// 云函数入口文件
const cloud = require('wx-server-sdk')// 初始化云函数
cloud.init()// 连接云数据库
const db = cloud.database()
const _ = db.command// 云函数入口函数
exports.main = async (event, context) => {try {return await db.collection('spend_items').where({accountKey: event.accountKey}).remove()} catch (e) {console.error(e)}
}

MVVM

界面有了,数据有了。万事俱备,只欠东风!所以下一步就是MVVM的设计。小程序本质就是基于MVVM所设计的,在MVVM的世界里,数据是灵魂,一切都由数据来驱动。

账本页显示

账本页有两种显示的风格,左上角的按钮可以来回切换风格,下拉可刷新页面,显示accounts数据表中存储的账本信息。显示时有个小细节,需要根据创建的时间先后来显示,越晚创建的越先显示。

// 页面数据设计, 在wxml中使用{{}}符号引用数据,数据就动态显示到了页面上
data: {isList: false, // 转换页面风格的标识 true为竖向风格 false为横向风格accounts: [],  // 存储查询的账本数据now: null,     // 存储当日时间year: null     // 存储年份
}// 转换显示风格
switchList() {// 设置页面风格样式let isList = !this.data.isListthis.setData({isList})wx.setStorage({key: "isList",data: isList})
}// 获取页面风格转换标识
var isList = wx.getStorageSync('isList')// 查询账本
db.collection('accounts').get({success: res => {this.setData({accounts: res.data.reverse(),  // 反转数组,优先显示创建早的账本isList})wx.hideLoading()}})// 调用云函数接口 获取当前日期
wx.cloud.callFunction({// 云函数接口名就是创建的云函数名字,这里是'getTime'name: 'getTime',success: (res) => {let year = res.result.split('-')[0]this.setData({now: res.result,year})},fail: console.error
})

账本页增删改

账本页通过调用相应的云数据库API,可进行一系列的增删改操作。值得一提的是,修改时需要表单回显,删除时需要级联删除。因为一个账本中有许多收支情况,spend_items表就是进行收支记录,所以删除账本时需要级联删除对应的spend_items表中的收支信息。

账本页收支

因为收入与支出页面基本类似,所以使用自定义组件封装,可以复用。

// 封装spendDetail组件
// 注册组件
properties: {detail: {type: Object},accountKey: {type: Number},isSpend: {type: Boolean}
}// 引用组件
<van-tab title="支出"><spendDetail detail="{{detail}}" accountKey="{{accountKey}}" isSpend="{{isSpend}}"></spendDetail></van-tab><van-tab title="收入"><spendDetail detail="{{income}}" accountKey="{{accountKey}}" isSpend="{{isSpend}}"></spendDetail>
</van-tab>

收入与支出类型icon选择使用两个view来存放,通过选择不同类型,跳转不同的icon

// js
data: {address: '',money: 0,desc: '',selectPicIndex: 0,selectIndex: 0
}
// 选择消费类别
selectSpend(e) {let { index } = e.currentTarget.datasetlet { selectPicIndex } = this.dataselectPicIndex = indexthis.setData({selectPicIndex})
},// 选择消费类别中的细节
selectSpendDetail(e) {let { index } = e.currentTarget.datasetlet { selectIndex } = this.dataselectIndex = indexthis.setData({selectIndex})
}// wxml
// 消费类型
<view class="expense"><block wx:for="{{detail}}" wx:key="index"><view class="expense__type" bindtap="selectSpend" data-index="{{index}}"><block wx:if="{{selectPicIndex == item.pic_index}}"><view class="expense__type-icon" style="background-color: #e64343"><image src="{{item.pic_url_act}}"></image></view></block><block wx:else><view class="expense__type-icon"><image src="{{item.pic_url}}"></image></view></block><view class="expense__type-name">{{item.type}}</view></view></block>
</view>// 消费子类型
<view class="detail"><block wx:for="{{detail[selectPicIndex].detail}}" wx:key="index"><view class="detail__type" bindtap="selectSpendDetail" data-index="{{index}}"><image class="detail__type-icon" src="{{item.detail_url}}"></image><block wx:if="{{selectIndex == item.detail_index}}"><view class="detail__type-name" style="color: #f86319; border-bottom: 1rpx solid #f86319;">{{item.detail_type}}</view></block><block wx:else><view class="detail__type-name" style="border-bottom: 1rpx solid #e4e2e2;">{{item.detail_type}}</view></block></view></block>
</view>

账本页明细

因为收支明细中需要显示每一天的消费信息,所以需要将数据表中的数据通过时间来分类,分成若干个数组,页面从而使用wx:for来遍历这些数组。在显示之前,首先需要判断有无收支信息。

// 通过时间分类算法  {} => [ [{时间1}], [{时间2}], [{时间3}] ]
arr.forEach(item => {if (!_this.isExist(item.fullDate, dateArr)) {dateArr.push([item])} else {dateArr.forEach(res => {if (res[0].fullDate == item.fullDate) {res.push(item)}})}
})// 使用map 方法构造 [{}, {}, {}, ...] 类型数组
dateArr = dateArr.map((item) => {let spend = 0let income = 0item.forEach(res => {if (res.money > 0) {spend += res.money} else {income += (-res.money)}})return {item,spend,income}
})// 判断自身是否存在数组中
isExist(item, arr) {for (let i = 0; i < arr.length; i++) {if (item == arr[i][0].fullDate)return true}return false}

以上是小程序中比较复杂的逻辑实现。

运用云开发,开发一份专属自己的旅行小账本吧~

小程序中里的bindinput_云开发实战分享|诗和远方:旅行小账本云开发相关推荐

  1. 小程序中里的bindinput_微信小程序输入框input

    微信小程序输入框input 属性名类型默认值说明 valueString 输入框的内容 typeStringtextinput的类型,有效值:text,number,idcard,digit,time ...

  2. java开发微信如何维护登录状态_微信小程序中做用户登录与登录态维护的实现详解...

    总结 大家都知道,在开发中提供用户登录以及维护用户的登录状态,是一个拥有用户系统的软件应用普遍需要做的事情.像微信这样的一个社交平台,如果做一个小程序应用,我们可能很少会去做一个完全脱离和舍弃连接用户 ...

  3. Canvas 动画引擎解析与微信小程序中的应用

    点击观看大咖分享 抗击疫情,腾讯云在行动.在开发微信小程序的过程中,我们经常需要展现一些图形和图表.目前市面上有好几款常用的图形库,在这些图形库的底层都有渲染引擎在支撑. ZRender 是其中一款非 ...

  4. 小程序 | 小程序中常用的事件 + 事件对象的属性列表 +小程序事件传参 + 小程序全局配置 + 小程序页面配置 + 小程序发起网络数据请求

    文章目录 一.WXML 模板语法 数据绑定 事件绑定 ⭐小程序中常用的事件 ⭐事件对象的属性列表 target 和 currentTarget 的区别 bindtap 的语法格式 在事件处理函数中为 ...

  5. 微信小程序中base64格式的小程序码通过canvas画出来无效

    使用场景 小程序中的文章详情页面有一个分享功能:用户点击分享按钮,生成一张分享图片(包括封面图,简介以及带有文章ID的小程序码),方便用户保存在本地. 问题说明 小程序码通过后台接口获取,格式如下:' ...

  6. 如何在微信小程序中集成认证服务—邮箱地址篇

    近期华为AppGallary Connect的认证服务SDK新增支持了微信小程序.今天就来教大家如何在微信小程序中集成认证服务的邮箱地址认证方式 1.安装微信小程序环境 首先进入微信小程序官网下载微信 ...

  7. 微信小程序中使用 npm 包

    在微信小程序中使用npm包,需要进行以下步骤: 确保你的小程序开发工具的版本号高于v1.02.1808300,因为这个版本之后的小程序开发工具已经支持使用npm包. 小程序根目录下执行npm init ...

  8. 支付宝小程序中使用F2图表

    支付宝小程序中使用F2图表 介绍 最近在支付宝小程序开发中接到显示图表的需求,因为支付宝小程序方未提供相关插件,并且目前支付宝小程序不支持document,所以根据推荐使用f2-canvas图表组件. ...

  9. 《十五》微信小程序中的插件

    插件是对一组 js 接口.自定义组件或页面的封装,用于嵌入到小程序中使用. 插件不能独立运行,必须嵌入在其他小程序中才能被用户使用:而第三方小程序在使用插件时,也无法看到插件的代码.因此,插件适合用来 ...

最新文章

  1. CMD一键获取 所有连接过的WIFI密码
  2. Github上十大热门可视化面板!再也不用担心画图啦!
  3. 更新至Android Studio4.1后发现as打不开的解决方案
  4. 实践操作--云端深度学习工作站配置指南(转)
  5. python xlrd模块_python之xlrd模块
  6. 边缘计算助力云游戏成为5G时代的杀手级应用
  7. jq ajax提交评论,织梦评论怎么改成自己的jq ajax评论
  8. linux系统用户锁定与解锁
  9. 笔记本电脑如何强制关机_笔记本按电源按钮不能关机只是关闭屏幕的解决办法...
  10. SpringBoot配置文件加密
  11. Iperf 源代码分析(四)
  12. PyTorch安装问题解决
  13. cesium 高程数据使用
  14. 个人静态博客页面模板
  15. 计算机专业英语课程内容,《计算机专业英语》课程教学大纲
  16. AWS Lambda学习2:通过S3事件触发调用Lambda函数,实现缩略图地生成
  17. 重磅|如何利用NBA球员推文预测其球场表现?
  18. 物联网无线通信技术 低功耗WiFi模块 WiFi芯片技术应用
  19. 杭州住房公积金提取说明
  20. 为什么word打字换行的时候突然上一行文字间距变大了?如图

热门文章

  1. WIN10什么都没开内存占用率过高, WIN7单网卡设置双IP
  2. 洛谷 P1451【细胞】
  3. 什么是可哈希的(hashable)
  4. Oracle数据库DBA必备基本技能
  5. centos 安装 Pip 的方法总结
  6. 时间对象与字符串对象之间相互转换
  7. [.Net]轻量ORM——Dapper
  8. 奔着政府补贴:野蛮生长的机器人产业或跳进去一家死一家
  9. Kmalloc和Vmalloc的区别
  10. 利用Windows的启动机制实现拦截360的运行