- 自定义navigationBar顶部导航栏,兼容适配所有机型(附完整案例)
前言 navigationBar相信大家都不陌生把?今天我们就来说说自定义navigationBar,把它改变成我们想要的样子(搜索框+胶囊、搜索框+返回按钮+胶囊等)。 思路 隐藏原生样式 获取胶囊按钮、状态栏相关数据以供后续计算 根据不同机型计算出该机型的导航栏高度,进行适配 编写新的导航栏 引用到页面 正文 一、隐藏原生的navigationBar window全局配置里有个参数:navigationStyle(导航栏样式),default=默认样式,custom=自定义样式。 [代码]"window": { "navigationStyle": "custom" } [代码] 让我们看看隐藏后的效果: [图片] 可以看到原生的navigationBar已经消失了,剩下孤零零的胶囊按钮,胶囊按钮是无法隐藏的。 二、准备工作 1.获取胶囊按钮的布局位置信息 我们用wx.getMenuButtonBoundingClientRect()【官方文档】获取胶囊按钮的布局位置信息,坐标信息以屏幕左上角为原点: [代码]const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); [代码] width height top right bottom left 宽度 高度 上边界坐标 右边界坐标 下边界坐标 左边界坐标 下面是官方给的示意图,方便大家理解几个坐标。 [图片] 2.获取系统信息 用wx.getSystemInfoSync()【官方文档】获取系统信息,里面有个参数:statusBarHeight(状态栏高度),是我们后面计算整个导航栏的高度需要用到的。 [代码]const systemInfo = wx.getSystemInfoSync(); [代码] 三、计算公式 我们先要知道导航栏高度是怎么组成的, 计算公式:导航栏高度 = 状态栏高度 + 44。 实例 【源码下载】 自定义导航栏会应用到多个、甚至全部页面,所以封装成组件,方便调用;下面是我写的一个简单例子: app.js [代码]App({ onLaunch: function(options) { const that = this; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); // 胶囊按钮位置信息 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 状态栏高度 + 44 that.globalData.navBarHeight = systemInfo.statusBarHeight + 44; that.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right; that.globalData.menuBotton = menuButtonInfo.top - systemInfo.statusBarHeight; that.globalData.menuHeight = menuButtonInfo.height; }, // 数据都是根据当前机型进行计算,这样的方式兼容大部分机器 globalData: { navBarHeight: 0, // 导航栏高度 menuRight: 0, // 胶囊距右方间距(方保持左、右间距一致) menuBotton: 0, // 胶囊距底部间距(保持底部间距一致) menuHeight: 0, // 胶囊高度(自定义内容可与胶囊高度保证一致) } }) [代码] app.json [代码]{ "pages": [ "pages/index/index" ], "window": { "navigationStyle": "custom" }, "sitemapLocation": "sitemap.json" } [代码] 下面为组件代码: /components/navigation-bar/navigation-bar.wxml [代码]<!-- 自定义顶部栏 --> <view class="nav-bar" style="height:{{navBarHeight}}px;"> <input class="search" placeholder="输入关键词!" style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{menuHeight}}px; left:{{menuRight}}px; bottom:{{menuBotton}}px;"></input> </view> <!-- 内容区域: 自定义顶部栏用的fixed定位,会遮盖到下面内容,注意设置好间距 --> <view class="content" style="margin-top:{{navBarHeight}}px;"></view> [代码] /components/navigation-bar/navigation-bar.json [代码]{ "component": true } [代码] /components/navigation-bar/navigation-bar.js [代码]const app = getApp() Component({ properties: { // defaultData(父页面传递的数据-就是引用组件的页面) defaultData: { type: Object, value: { title: "我是默认标题" }, observer: function(newVal, oldVal) {} } }, data: { navBarHeight: app.globalData.navBarHeight, menuRight: app.globalData.menuRight, menuBotton: app.globalData.menuBotton, menuHeight: app.globalData.menuHeight, }, attached: function() {}, methods: {} }) [代码] /components/navigation-bar/navigation-bar.wxss [代码].nav-bar{ position: fixed; width: 100%; top: 0; color: #fff; background: #000;} .nav-bar .search{ width: 60%; color: #333; font-size: 14px; background: #fff; position: absolute; border-radius: 50px; background: #ddd; padding-left: 14px;} [代码] 以下是调用页面的代码,也就是引用组件的页面: /pages/index/index.wxml [代码]<navigation-bar default-data="{{defaultData}}"></navigation-bar> [代码] /pages/index/index.json [代码]{ "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" } } [代码] /pages/index/index.js [代码]const app = getApp(); Page({ data: { // 组件参数设置,传递到组件 defaultData: { title: "我的主页", // 导航栏标题 } }, onLoad() { console.log(this.data.height) } }) [代码] 效果图: [图片] 好了,以上就是全部代码了,大家可以文中复制代码,也可以【下载源码】,直接到开发者工具里运行,记得appid用自己的或者测试哦! 下面附几张其它小程序的效果图,大家也可以尝试照着做: [图片][图片] 总结 本文写了自定义navigationBar的一些基础性东西,里面涉及组件用法、参数传递、导航栏相关。 由于测试环境有限,大家在使用时如果发现有什么问题,希望及时反馈,以供及时更新帮助更多的人! 大家有什么疑问,欢迎评论区留言!
2022-06-23 - 教大家用20行js代码,开发好小程序订阅消息
微信小程序官方决定在2020-1-10全面线下小程序模板消息,要去替换为订阅消息。那对开发者而言,又要一个一个地方去修改代码兼容.... 所以我替大家写了段代码,来快速解决问题。复制下面这段代码到app.js文件最上面即可解决问题。代码的主要功能是在每一个tap类型的点击事件中触发订阅弹窗,这样用户点几次界面,你就可以发几次消息。这也是让发送次数最大化,不可能比这个次数还多了。 预期结果是:用户点几次弹窗,就会注意到有一个不再提醒按钮,一旦选了它,那你就可以随便发订阅消息了! // 记录原Page方法 const originPage = Page; // 重写Page方法 Page = (page) => { Object.keys(page).forEach(function(key){ if(key !== 'data'){ let originMethod = page[key]; page[key] = function () { let e = arguments[0]; //给所有的点击事件增加订阅消息弹窗 if(!!e && !!e.type && e.type === 'tap'){ wx.requestSubscribeMessage({ tmplIds: ['3E66jPXafsnikZoQR5uk0OUzIUVASZE5scyAu5YCHPI'], ////////这里替换为自己的模板ID///// success (res) { // console.log(res) }, fail (res) { // console.log('订阅消息失败',res) } }) } return originMethod.call(this,...arguments) } } }); return originPage(page); };
2020-01-09 - 那些被忽略的盒子模型小知识
那些被忽略的盒子模型小知识 本文是笔者在学习CSS时的一些小白总结 我们知道的盒子模型主要由4个区域组成,分别是内容区域(content),内边距区域(padding),边框区域(border)和外边距区域(margin)。 对于不了解盒子模型的朋友可以移步到这里了解一下。 [图片] Content(内容) 1. 替换元素 替换元素(replaced element),顾名思义就是内容可以被替换的元素。 我们通常会把一些特殊意义的文本替换成图片,比如一个网站的logo。 [图片] 我们会在页面上看到的不是h1标签显示的”Google“文字,而是谷歌logo的图片。使用了content的元素的内容在html标签中是不存在的。这样做就有个好处,当爬虫来访问我们的网站,爬虫可以知道我们这个主站的h1标题是”Google“而不是一个img标签,且在视觉上给用户更好的体验。 2. 伪元素::before和::after [图片] 为了实现上面显示价格,之前写react代码时候会经常这么写,感觉在逻辑上写了好多关联性不大的文本。其实可以利用[代码]::before[代码]和[代码]::after[代码]两个伪元素,把这些与逻辑不相关的写在css里,react dom则专注于数据的表现。 [代码]<div className="price-panel"> <span className="price-panel__price"> ¥ {(totalPrice / 100).toFixed(1)} </span> <span className="price-panel__discount-price"> 已省¥ {(totalDiscountPrice / 100).toFixed(1)} </span> <span className="price-panel__discount"> ( {(discount / 10).toFixed(1)} 折) </span> </div> [代码] 使用了伪元素后的react代码显然更加清晰表示数据。 HTML: [代码]<div className="price-panel"> <span className="price-panel__price"> {(totalPrice / 100).toFixed(1)} </span> <span className="price-panel__discount-price"> {(totalDiscountPrice / 100).toFixed(1)} </span> <span className="price-panel__discount"> {(discount / 10).toFixed(1)} </span> </div> [代码] SCSS: [代码].price-panel { &__price { &::before { content: '¥'; } } &__discount-price { &::before { content: '已省¥'; } } &__discount { &::before { content: '('; } &::after { content: '折)'; } } } [代码] 我们还能使用伪元素帮助实现一些本来需要多个div实现的样式,比如下面这个对话框。 [图片] HTML: [代码]<div class="dialog">Hi,I’m a bubble dialog. Can you see me?</div> [代码] CSS: [代码].dialog { background: #f0f; padding: 10px; border-radius: 10px; color: white; max-width: 250px; position: relative; overflow: visible; } .dialog::after { position: absolute; content: ''; display: inline-block; border-width: 5px 10px; border-style: solid; border-color: transparent transparent #f0f #f0f; width: 0; height: 0; right: -20px; } [代码] Padding(内边距) padding的百分比值是非常有用的。需要注意的padding的百分比值,无论是水平方向还是垂直方向都是相对于父级元素的宽度进行计算的。 如果需要弄一张16:9的等比缩放图片,可以利用padding的这个特性,设置一个[代码]padding-top[代码]或者[代码]padding-bottom[代码]为56.25%即可(100\16*9) [图片] [图片] Margin(外边距) 1. margin合并 块级元素的[代码]margin-top[代码]和[代码]margin-bottom[代码]有时候会合并为单个margin,这种现象叫margin合并。 margin合并发生两个重要元素 必须是块级元素 只发生在垂直方向。 margin合并的场景 1.1 相邻兄弟元素 [图片] 1.2 父级和第一个/最后一个子元素 在实际开发中,父子margin合并很有可能会带给我们麻烦。 如下图所示,div表现出和我们预想不一致的结果。 [图片] 那么怎么才能防止这种父子margin合并导致的和预想不一致问题呢? 解决方法如下(这里直接复制了张鑫旭老师书籍《CSS世界》的原话。): (1)对于margin-top合并(满足一个即可): 父元素设置为BFC 设置[代码]border-top[代码]的值(亲测transparent也可以的) 设置[代码]padding-top[代码]的值 父元素和第一个子元素之间添加内联元素 (2)对于margin-bottom合并(满足一个即可): 父元素设置为BFC 设置[代码]border-bottom[代码](transparent也可以的) 设置[代码]padding-bottom[代码] 父元素和最后一个子元素之间添加一个内联元素 父元素设置[代码]height[代码]、[代码]min-height[代码]或者[代码]max-height[代码] 1.3 空块级元素的margin合并 [图片] 2. margin auto 每当说到[代码]margin:auto[代码],我的第一反应是居中。但这个只是一个浅层应用的表象。 接下来我们去一起看看这个[代码]margin:auto[代码]究竟是‘何方神圣’。 [代码]margin:auto[代码]的填充规则如下: 如果一侧定值,一侧auto,则auto为剩余空间大小。注意auto并不是0的意思。 如果两侧都是auto,则平分剩余的空间 我会疑惑为什么我设置了[代码]margin: auto[代码],却在垂直方向上没有居中。 [图片] 这里《css世界》中给出的答案让人非常容易理解。假如把.son元素的height去掉,.son的高度会自动变成父元素的200px,显然不会,所以无法触发margin: auto。同理,如果把width为200px去掉,确实是会和父元素一样宽。 那么如何让垂直居中呢? 子元素使用绝对定位后设置[代码]margin: auto[代码]即可 [图片] Border(边框) 用border绘制三角形 我们可以利用border color为透明来绘制一些图形,比如三角形 [图片] 注意[代码]border-color[代码]这个属性。 [代码]/* border-color: color; 单值语法 */ border-color: red; /* border-color: vertical horizontal; 双值语法*/ border-color: red #f015ca; /* border-color: top horizontal bottom; 三值语法 */ border-color: red yellow green; /* border-color: top right bottom left; 四值语法 */ border-color: red yellow green blue; [代码] 当然,我们绘制三角形不限于这种等腰三角。 [图片] 这里绘制了一个底边分别是60px和160px的直角三角形。 参考 文章主要参考了张鑫旭老师的《css世界》并根据自己的业务做出的一些实践总结。
2019-04-28 - 对于一些骗子使用微信支付商业版的问题!?
我不是来抬杠的,那一天我遇到一个网站lcwu.cn,来看看他的网站首页[图片] 妥妥的诈骗平台,高收益,日化利息?????? 然后我就想投诉这家伙,发现微信支付一分钱是无法投诉的(难道是给的钱太少???)[图片] 今天我又试了一下,仍然无法投诉,我就想问微信客服why? 然而微信客服电话打不通?我曹这服务质量....... 然后我就跟骗子聊天那[图片] 唉,真的是嚣张啊~ 再此之前呢我也遇到过这种诈骗平台,我也是付了一分钱,然后他居然给退回来了,退了我一分钱,然后我居然能投诉了???? 然而没有这么简单~ [图片] 系统繁忙?微信客服我就想问这个几个意思?
2019-04-04 - 云开发实战分享|诗和远方:旅行小账本云开发
原创:豪豪 前言 最近沉迷小程序开发,发现了一款功能、界面、体验俱佳的小程序“旅行小账本”。着手做了个简约版——“旅行小账本”。效果比较满意,毕竟前后台一人单干。 [图片] 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 - 项目完全使用小程序云开发的填坑之路~
产品定位:轻婚恋社交。 愿景:年轻人的交友、婚恋好助手。 主要功能: 用户信息维护 首页推荐 IM 平台积分:红豆 后续功能扩展方向(社区、用户动态等等) 技术需求: 数据存储(微信云数据库) 文件存储(微信云存储) 支付(知晓云) CMS(知晓云) 模板消息(知晓云) IM(极光IM) (目前微信小程序云开发服务提供上比较多,做的比较早的有知晓云、野狗、bmob等等,不过我把主要逻辑和数据放在了微信云开发上面,因为是亲儿子嘛。哈哈哈) 重点说下IM选型的过程,因为前期是个人项目(现在已经团队化公司化运作),用户体验、成本是最重要考虑的因素。(其实成本是最主要的考虑,哈哈,因为没钱) 选型标的:腾讯通信云、网易云信、极光IM 腾讯通信云999元/月送10万日活,网易通信云1800元/月送1万日活、极光IM基础功能全免费 (其实在选型过程中找了很多服务商的商品,但是因为价格吓到我了,所以就不列举太多了啊,比如融云、环信、野狗等) 在选型体验中,产品demo做的最好的是网易云信,几乎可以拿来就直接上线了。 [代码]腾讯通信云demo只能说还过得去(考虑到是大公司做的,其实我内心评价是烂的要命),不过如果自身产品比较成熟、规模比较大的话,腾讯通信云是最好的选择,价格划算,性能稳定。 因为实在是穷,所以选了完全免费的极光IM,极光IM的demo比腾讯通信云好,比网易云信demo差一点,不过谁让我只差钱呢,哈哈,所以前期考虑极光IM,后续如果量大会迁移到腾讯通信云上。 [代码] 言归正传说说项目中走过的坑和告诉你怎么不要掉坑 坑1、云开发限额 建议1、基本上默认的限额只是给你开发测试用的,要上线和正常使用,建议提前给微信发邮件申请扩容 邮箱是:miniprogram@tencent.com 坑2、数据库不提供联表查询 建议2、业务逻辑上涉及到多表查的,建议使用云函数来实现,因为云函数部署在靠近云数据库的机房,理论上比使用小程序直接查询性能和体验上要好的多 坑3、云开发目前没有提供open api接口 项目上会涉及到运营后台、还有数据分析啥的 建议3.1、运营后台建议在现有小程序开一个隐秘入口 建议3.2、如果是涉及到个性化推荐和数据分析,按目前小程序官方云开发提供的服务是不能满足的,所以在做相关业务数据存储的时候,建议也写一份数据到另外的第三方云服务(比如知晓云) (后续感觉官方会提供open api接口,只是不知道啥时候,可能猴年马月吧,你懂的……) 坑4、云控制台数据库提供的数据导入功能很鸡肋 详情请看链接: https://developers.weixin.qq.com/community/develop/doc/000c46047a877882bfb7a69515b400 https://developers.weixin.qq.com/community/develop/doc/000ee271fbcc7812bd67e97b355400 建议4、做一个全局数据库初始化函数,把数据导入用函数来实现。(比如创建表、初始化数据啥的) 坑5、云函数环境配置目前没有比较优雅的统一管理方式 建议5、详情请看社区中一哥们的建议: https://developers.weixin.qq.com/community/develop/doc/000e2acb0589683fced77fdf553404 总结一下他的建议:提供一个主函数入口,实现其他云函数的路由。 缺点是:如果云函数太多,就会加载很慢,因为云函数在官方定义是轻量级的,用户请求的时候才加载文件,处理完请求后可能会把资源释放。 终极坑、官方云开发目前还处于社会主义初级阶段(哈哈哈),遇到问题还会比较多 终极建议、做好项目技术选型和需求调研 如果遇到问题掉坑实在走不出来,请记得把坑的详情记录下来,发到社区。(建议不要找官方客服,那只会浪费你的时间。哈哈) (持续更新中……)
2019-02-21 - 聊一聊小程序开发中的单位如何布局使用?
小程序支持的单位? 可以说小程序就是在微信体系网页的另一种表现方式。网页中的单位小程序基本都支持。但实际开发中,我常用到的是以下几种 ↓ rpx rpx做为小程序自家系统里的单位,特性是可以根据屏幕宽度进行自适应。rpx官方介绍 比如我写一个2:1比例的全屏轮播图,可以这样写: [代码]swiper { width:750rpx; height:375rpx; } [代码] 1rpx = 0.5px = 1物理像素。网页开发中,默认字体一般设置为14px,在小程序中我们就可以设置小程序的默认字体大小为28rpx。 px 在小程序开发中 rpx基本就代替了px,但在一些特殊的场合,px的表现要比rpx好。 兼容ipad时,由于ipad可以横屏和竖屏,并且屏幕宽度可以达到2K以上,如果你的小程序要考虑到兼容ipad,那么还是多考虑使用px吧。 覆盖微信原生组件样式。em????可以覆盖微信原生样式??? 是的,只有小程序老玩家才知道的秘密!小程序原生样式是可以覆盖美化的,以 <switch> 组件为例:switch代码片段 [图片] 导入代码片段到开发者工具中,并切换设备模式预览可以发现rpx表现不佳。使用px反而更好。 em与rem em与rem在H5的网页开发上可以大放异彩,但小程序中因为有rpx的存在,em与rem使用的就少了。基本只有在一些对字体宽度有特效的情况下才会使用。比如首行缩进。 vw、vh和百分比 vw:视窗宽度,1vw等于视窗宽度的1%。 vh:视窗高度,1vh等于视窗高度的1%。 %:父级容器的宽度百分百。 [图片] calc() 的使用 前面讲了单位,那么我们现在来聊聊怎么使用这些单位了。小程序是网页的一种,支持css,也支持calc()。 这里吃下书: calc() 函数用于动态计算长度值。 [代码] ● 需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px); ● 任何长度值都可以使用calc()函数进行计算; ● calc()函数支持 "+", "-", "*", "/" 运算; ● calc()函数使用标准的数学运算优先级规则; [代码] 使用场景示例 垂直导航页,常用于外卖订餐或者商城的二级分类页。 上半部分是定死高度375rpx的轮播图区域,下半部分是可以随设备高度变化的可滚动的区域。容器高度可以这样写: [代码]{ height:calc(100vh - 375rpx) } [代码] [图片] 结尾 夜深了,晚安,不定期更新小程序使用技巧。新人写文章,大佬多指点! [图片]
2019-02-26 - getPhoneNumber接口的一点建议
[图片] 如果微信用户没有绑定手机号,调用getPhoneNumber接口会弹出这个框,而这个框的“取消”按钮事件我们开发者却监听不到,作为正常用户(微信用户现在都是绑定了手机号的吧),即使没绑定,也可以点击右边按钮去绑定,然后登陆我们的小程序。问题来了: 我们的项目设计只有手机号登陆,倒是可以提供一个测试账号登录给审核,但放在“拒绝”手机授权(当微信绑定的有手机号时弹出的手机授权框)后弹出,但是如果你的微信没有绑定过手机号,拒绝手机授权那个框微信不会给你弹出来,而是弹出上面那个框,而这个框我们捕捉不到用户点击取消后的事件,无法做出相应处理 ,当然正常的用户是可以点击绑定手机号继续进行,但微信的审核人员没有电话卡,所以直接拒绝了,拒绝理由是审核人员测试机无法完成此操作,而我放在拒绝手机授权时弹出的账号密码登录框也没地方弹出来,因为那个授权框根本不会弹出来 我感觉官方可以考虑在1.getPhoneNumber这个接口加上failed回调(也就是检测到微信没有绑定手机号这种情况) 2.不要弹出上面第一张这种我们开发者不可控的弹框,仍然弹出[图片],但是提示未绑定手机号,此时的“拒绝”按钮事件,我们开发者就能捕捉到了 3.给审核人员配一张电话卡吧,这就好比我要吃饭,但是连筷子都没有,肯定吃不了哇, 如果是因为这个原因拒绝吃饭,是否有点尴尬
2019-02-27 - 微信小程序开发-76种动画 animate.css
1、微信小程序动画有自己的方法:官方链接 但需要自己去写动画效果,比较麻烦。 2、本文介绍的是把animate.css这个非常棒的css库引入到小程序内使用。 animate.css包含76种动画,使用非常简单。animate.css官网 : https://daneden.github.io/animate.css/ 3、由于小程序对代码大小限制比较大,所以删除了animate.css中 所有@-webkit-部分css,减少了一半体积 再把文件后缀名改为wxss,以后出来的百度小程序、支付宝小程序、今日头条小程序估计修改对应的后缀名就可以直接使用了。 下载地址:http://nodejs999.com/animate.wxss 4、放到小程序代码中,然后@import到app.wxss文件中。 我项目是把animate.wxss文件放在utils文件夹下。 所以在app.wxss中加入 @import './utils/animate.wxss'; 即可。 就可以像animate.css一样使用了。
2018-11-01