- mpvue+云开发,纯前端实现婚礼邀请函小程序
作者:黄彬 前言 感谢OnceLove提供的思路,借助他的小程序的界面UI风格,自己重新用mpvue实现了属于自己的婚礼邀请函,前前后后花了3天时间。 在这之前本人是没想过要自己实现这样一个项目,原因是后台那块是个麻烦事,所以当媳妇让我自己实现这个邀请函的时候,起初我是拒绝的。后面由于快要过年了,公司项目没有重大更新,趁着这段空闲时间,自己研究了下小程序自带的云开发,无需后台支持,前后端都可以自己来实现,下面我将一一介绍本项目的实现过程 ! 本小程序为婚礼正式使用的小程序,婚期将至,感兴趣的可以扫码体验本项目,沾沾喜气,欢迎大家体验,有什么问题可以在本文给我留言。 [图片] 源码地址: https://gitee.com/roberthuang123/wedding 准备工作 mpvue框架 mpvue官方文档 http://mpvue.com/mpvue/ 小程序·云开发 小程序·云开发文档 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html 注意:使用mpvue前,首先你得先熟悉vue框架的基本使用 项目结构介绍 [图片] common目录: 放一些公共资源,如js,css,json components目录:组件相关的.vue文件都放在这里 pages目录:所有页面都放在这个目录 utils目录:使用mpvue时自动生成,可忽略 app.json文件: [代码]{ "pages": [ "pages/index/main", "pages/photo/main", "pages/map/main", "pages/greet/main", "pages/message/main" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "tabBar": { "color": "#ccc", "selectedColor": "#ff4c91", "borderStyle": "white", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/main", "iconPath": "static/images/1-1.png", "selectedIconPath": "static/images/1-2.png", "text": "邀请函" }, { "pagePath": "pages/photo/main", "iconPath": "static/images/2-1.png", "selectedIconPath": "static/images/2-2.png", "text": "甜蜜相册" }, { "pagePath": "pages/map/main", "iconPath": "static/images/3-1.png", "selectedIconPath": "static/images/3-2.png", "text": "酒店导航" }, { "pagePath": "pages/greet/main", "iconPath": "static/images/4-1.png", "selectedIconPath": "static/images/4-2.png", "text": "好友祝福" }, { "pagePath": "pages/message/main", "iconPath": "static/images/5-1.png", "selectedIconPath": "static/images/5-2.png", "text": "留言评论" } ] }, "requiredBackgroundModes": ["audio"] } [代码] App.vue文件:本人主要是为了增加项目更新后的提醒,所以在这个文件加了些相关内容,内容如下: [代码]<script>export default { onLaunch () { // 检测小程序是否有新版本更新 if (wx.canIUse('getUpdateManager')) { const updateManager = wx.getUpdateManager() updateManager.onCheckForUpdate(function (res){ // 请求完新版本信息的回调 if (res.hasUpdate) { updateManager.onUpdateReady(function () { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success: function (res) { if (res.confirm) { // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate() } } }) }) // 小程序有新版本,会主动触发下载操作(无需开发者触发) wx.getUpdateManager().onUpdateFailed(function () { // 当新版本下载失败,会进行回调 wx.showModal({ title: '提示', content: '检查到有新版本,下载失败,请检查网络设置', showCancel: false }) }) } }) } else { // 版本过低则无法使用该方法 wx.showModal({ title: '提示', confirmColor: '#5BB53C', content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。' }) } }} </script> <style lang="stylus"> page height 100% image display block </style> [代码] main.js文件: [代码]import Vue from 'vue' import App from './App' Vue.config.productionTip = falseApp.mpType = 'app' wx.cloud.init({ env: '云开发环境ID' }) const app = new Vue(App) app.$mount() [代码] functions目录:主要放一些云函数,这里不清楚云函数的文章后面会提及 images目录:主要放一些静态资源图片 页面介绍 首页——邀请函 首页着重和大家讲解下背景音乐的实现方法 const audioCtx = wx.createInnerAudioContext() 首先,wx.createInnerAudioContext 接口获取实例 接着,通过实例的相关方法来实现音乐的播放与暂停功能 关于小程序音频相关文档 https://developers.weixin.qq.com/miniprogram/dev/api/InnerAudioContext.html 具体代码如下: [代码]<script> import IndexSwiper from 'components/indexSwiper' import tools from 'common/js/h_tools' const audioCtx = wx.createInnerAudioContext() export default { name: 'Index', components: { IndexSwiper }, data () { return { isPlay: true, list: [] } }, onShow () { const that = this that.isPlay = true that.getMusicUrl() }, methods: { audioPlay () { const that = this if (that.isPlay) { audioCtx.pause() that.isPlay = false tools.showToast('您已暂停音乐播放~') } else { audioCtx.play() that.isPlay = true tools.showToast('背景音乐已开启~') } }, getList () { const that = this const db = wx.cloud.database() const banner = db.collection('banner') banner.get().then(res => { that.list = res.data[0].bannerList }) }, getMusicUrl () { const that = this const db = wx.cloud.database() const music = db.collection('music') music.get().then(res => { let musicUrl = res.data[0].musicUrl audioCtx.src = musicUrl audioCtx.loop = true audioCtx.play() that.getList() }) } }, onShareAppMessage: function (res) { return { path: '/pages/index/main' } } } </script> [代码] 以上代码中使用到了云开发相关功能,文章后面会介绍,请大家稍安勿躁 相册页——就一个轮播图,这里就不过多介绍 地图页——这里着重讲一下地图标签map map标签 关于map组件的使用文档 这里讲一下标记点markers [代码]data () { return { // qqSdk: '', markers: [{ iconPath: '../../static/images/nav.png', id: 0, latitude: 30.08059, longitude: 115.93027, width: 50, height: 50 }] } } [代码] [代码]<template> <div class="map"> <image mode="aspectFit" class="head-img" src="../../static/images/t1.png"/> <map class="content" id="map" longitude="115.93027" latitude="30.08059" :markers="markers" scale="18" @tap="toNav"> </map> <div class="call"> <div class="left" @tap="linkHe"> <image src="../../static/images/he.png"/> <span>呼叫新郎</span> </div> <div class="right" @tap="linkShe"> <image src="../../static/images/she.png"/> <span>呼叫新娘</span> </div> </div> <image class="footer" src="../../static/images/grren-flower-line.png"/> </div> </template> [代码] 祝福页——也是云开发相关内容,后面会介绍 留言页——也是云开发相关内容,后面会介绍 云开发介绍 project.config.json文件: [代码]"cloudfunctionRoot": "static/functions/" [代码] 进行云开发首先我们需要找到上面这个文件,在上面这个json文件中加上上面这句 [代码]cloudfunctionRoot[代码] 用于指定存放云函数的目录 app.json文件: [代码]"window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "cloud": true [代码] 增加字段 [代码]"cloud":true实现云开发能力的兼容性[代码] 开通云开发 在开发者工具工具栏左侧,点击 “云开发” 按钮即可开通云开发 云开发控制台 [图片] 数据库 云开发提供了一个 JSON 数据库 存储 云开发提供了一块存储空间,提供了上传文件到云端、带权限管理的云端下载能力,开发者可以在小程序端和云函数端通过 API 使用云存储功能。 云函数 云函数是一段运行在云端的代码,无需管理服务器,在开发工具内编写、一键上传部署即可运行后端代码。 下面开始讲解使用云开发的过程: 云能力初始化,在小程序端开始使用云能力前,需先调用 [代码]wx.cloud.init[代码] 方法完成云能力初始化 [代码]import Vue from 'vue' import App from './App' Vue.config.productionTip = false App.mpType = 'app' wx.cloud.init({ env: '云开发环境ID' }) const app = new Vue(App) app.$mount() [代码] 数据库的使用: 在开始使用数据库 API 进行增删改查操作之前,需要先获取数据库的引用。 以下调用获取默认环境的数据库的引用: [代码]const db = wx.cloud.database() [代码] 要操作一个集合,需先获取它的引用: [代码]const todos = db.collection('todos') [代码] 接下来是操作数据库的相关示例: 例:首页获取背景音乐资源 [代码]getMusicUrl () { const that = this const db = wx.cloud.database() const music = db.collection('music') music.get().then(res => { let musicUrl = res.data[0].musicUrl audioCtx.src = musicUrl audioCtx.loop = true audioCtx.play() that.getList() }) } [代码] 例:首页获取轮播图数组 [代码]getList () { const that = this const db = wx.cloud.database() const banner = db.collection('banner') banner.get().then(res => { that.list = res.data[0].bannerList }) } [代码] 例:祝福页,用户送上祝福存储用户 [代码]<script>import tools from 'common/js/h_tools' export default { name: 'Greet', data () { return { userList: [], openId: '', userInfo: '' } }, onShow () { const that = this that.getUserList() }, methods: { scroll (e) { console.log(e) }, sendGreet (e) { const that = this if (e.target.errMsg === 'getUserInfo:ok') { wx.getUserInfo({ success: function (res) { that.userInfo = res.userInfo that.getOpenId() } }) } }, addUser () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.add({ data: { user: that.userInfo } }).then(res => { that.getUserList() }) }, getOpenId () { const that = this wx.cloud.callFunction({ name: 'user', data: {} }).then(res => { that.openId = res.result.openid that.getIsExist() } ) }, getIsExist () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.where({ _openid: that.openId }).get().then(res => { if (res.data.length === 0) { that.addUser() } else { tools.showToast('您已经送过祝福了~') } }) }, getUserList () { const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) } } } </script> [代码] 进入祝福页,首先我们需要获取送祝福的好友列表 [代码]getUserList () { const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) } [代码] 这里用到了云函数,之所以用云函数是因为小程序端在获取集合数据时服务器一次默认并且最多返回 20 条记录,云函数端这个数字则是 100。 下面给大家看看云函数的使用方法: 上面我们讲过在project.config.json文件中配置云函数存放位置 下面是云函数messageList的index.js文件: [代码]const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const MAX_LIMIT = 100 exports.main = async (event, context) => { // 先取出集合记录总数 const countResult = await db.collection('message').count() const total = countResult.total // 计算需分几次取 const batchTimes = Math.ceil(total / 100) // 承载所有读操作的 promise 的数组 const tasks = [] for (let i = 0; i < batchTimes; i++) { const promise = db.collection('message').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() tasks.push(promise) } // 等待所有 return (await Promise.all(tasks)).reduce((acc, cur) => ({ data: acc.data.concat(cur.data), errMsg: acc.errMsg })) } [代码] [图片] 使用云函数前,在开发者工具上,找到messageList目录,右键如图: [图片] 点击上传并部署:云端安装依赖(不上传node_modules) 得到如图的提示: [图片] 安装完点击完成就能使用当前云函数了,使用方法即: [代码]getUserList () { const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) } [代码] 数组之所以要倒序是因为希望新祝福的的用户在最前面显示 接着是用户送上祝福的时候存储用户 这里我们用到了云函数获取用户信息, 当小程序端调用云函数时,云函数的传入参数中会被注入小程序端用户的 openid,开发者无需校验 openid 的正确性,因为微信已经完成了这部分鉴权,开发者可以直接使用该 openid 下面是云函数user的index.js文件: [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID } } [代码] 主要是为了获取当前操作用户的openid,获取当前用户的openid方法: [代码]getOpenId () { const that = this wx.cloud.callFunction({ name: 'user', data: {} }).then(res => { that.openId = res.result.openid that.getIsExist() }) } [代码] 接着判断当前用户是否已经存在于数据库中,即getIsExist()方法: [代码]getIsExist () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.where({ _openid: that.openId }).get().then(res => { if (res.data.length === 0) { that.addUser() } else { tools.showToast('您已经送过祝福了~') } }) } [代码] 如果得到的数组长度为零则添加改用户到数据库,否则则提醒当前用户已经送过祝福 接下来介绍存储用户信息的方法,即addUser(): [代码]addUser () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.add({ data: { user: that.userInfo } }).then(res => { that.getUserList() }) } [代码] 存入到数据库的信息是这样的: [图片] 有人要看数据库字段,其实很简单,这是首页轮播图的字段,见下图: [图片] 这是留言的评论,如图: [图片] 总结 在这里给大家讲解几点细节: 云开发是免费的; 数据库集合名首先必须要建立; 数据库集合为空时,这里本人没有处理细节,会报错,先通过使用自己的小程序写入一条数据,错误就可以解决了; 云函数新建后一定要创建并安装依赖; 如果担心有些评论不太友好希望有删除功能,可以给自己开放权限,在每条评论加一个删除按钮,仅自己使用的时候通过唯一的openid控制删除按钮为可见,其他人不可见,从而来管理留言列表; 数据库建立集合后记得将权限放开,如下图: [图片] 最后总结: 除了一些静态资源放在项目中,其他资源建议一律存储在云开发-存储管理中,这样的话,方便更换资源而不用再次提交等待小程序审核,更换后的资源可以立即生效; 我知道大家都喜欢拿来就能直接看到同样效果的东西,本项目因为涉及到后台数据,不可能完全把本人的环境放出来给大家使用,建议真的要实现相关功能或类似项目的小伙伴对照我的思路重新做一个属于自己的作品才最有意义。
2019-02-27 - 云开发实战分享|诗和远方:旅行小账本云开发
原创:豪豪 前言 最近沉迷小程序开发,发现了一款功能、界面、体验俱佳的小程序“旅行小账本”。着手做了个简约版——“旅行小账本”。效果比较满意,毕竟前后台一人单干。 [图片] 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 全局js app.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 消费类型图片 [代码] 云储存管理 这是个非常实用的板块。类似于<a href=“https://pan.baidu.com/”>百度云盘</a>,它提供了文件存储、上传与下载功能。 [图片] 除此之外,它还会将你所上传的资源自动进行压缩操作,并生成一个地址供你引用。该项目中的一些图片资源就是存在于此,然后在云数据库的字段中引用这些资源地址即可,十分方便,不必在本地存储,占用小程序内存。 [图片] 云函数设计 云函数简单来说就是在云后端(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() + 1 var 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 + strDate return 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.isList this.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表中的收支信息。 一些重要的逻辑 封面单选逻辑[代码]data: { images: [], // 封面数组 selectImg: null, // 选择其它封面 isSelected: {}, // 选中的图片 inputValue: '', // 账本名字 now: null, // 当前时间 account: {} // 传入账本信息 } // 单选逻辑 通过构造{'0': isSelected}来实现 selectThis(e) { let index = e.currentTarget.dataset.index let coverUrl = e.currentTarget.dataset.coverurl let is = this.data.isSelected[index] let obj = { coverUrl } // obj[index] 属性动态改变 obj[index] = !is obj.i = index this.setData({ isSelected: obj }) } [代码] 表单回显逻辑[代码]// 页面加载时先通过对应的accountKey, 得到回显信息 let { i, id, value, url, accountKey } = options photos.get({ success: res => { this.setData({ images: res.data, account: { id, value, url, i, accountKey }, isSelected: obj }) wx.hideLoading() } }) // 修改 save() { let { id } = this.data.account let { i, coverUrl, value } = this.data.isSelected // 若没修改 则为之前的value let inputValue = this.data.inputValue || value [代码] db.collection(‘accounts’) .doc(id) .update({ data: { inputValue, coverUrl, i } }) } ``` 级联删除逻辑[代码]db.collection('accounts') .doc(this.data.account.id) .remove() .then(() => { wx.hideLoading() wx.showToast({ title: '删除成功' }) setTimeout(() => { wx.reLaunch({ url: '../accountBooks/accountBooks' }) }, 400) }) // 调用deleteItems云函数, 传入对应accountKey主键, 通过云函数批量删除 wx.cloud.callFunction({ name: 'deleteItems', data: { accountKey } }) [代码] 账本页收支 [图片] 因为收入与支出页面基本类似,所以使用自定义组件封装,可以复用。 [代码]// 封装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.dataset let { selectPicIndex } = this.data selectPicIndex = index this.setData({ selectPicIndex }) }, // 选择消费类别中的细节 selectSpendDetail(e) { let { index } = e.currentTarget.dataset let { selectIndex } = this.data selectIndex = index this.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 = 0 let income = 0 item.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 } [代码] 以上是小程序中比较复杂的逻辑实现。 运用云开发,开发一份专属自己的旅行小账本吧~
2019-03-15