标签归档:前端开发

微信小程序引入promise相关库及Android真机报错解决方案 -演道网

前言

如果你恰巧使用的是bluebird,又恰巧你只是在模拟器上测试或者你的真机是iOS系统,你有必看看到最后,因为在Android的真机下,bluebird会报错

建议阅读对象: 在小程序中使用Promise或者使用bluebird报错的对象。

如果你现在还不知道Promise是什么,建议先去查看es6后再看,不然你不知道我在解决的问题

问题描述

2016.11.22微信官方开发工具更新了,版本为0.11.112200,结果在上面跑小程序代码的时候,发现报错Promise is not a constructor.
初步判断应该是Promise在小程序代码中不能直接使用。

问题查找

F:修复 同客户端保持一致,移除 Promise,开发者需要自行引入兼容库
这种转换只会帮助开发处理语法上问题,新的 ES6 的 API 如 Promise等需要开发者自行引入 Polyfill 或者别的类库。

解决方案

先说正确的解决方案,但是心酸的bluebird,你可以继续看下去,坑:别用bluebird

既然官方给出了相关意见,那么我们就采用官方的意见自行引入 Polyfill,如果你不小心引入了bluebird库,请看到最后。

  • 找库
    我找到了es6-promise-polyfill: https://github.com/stefanpenner/es6-promise min版只有6KB,很合适。
  • 使用
    常规使用即可

    import Promise from 'es6-promise.min';
    export const request = (method = 'GET') => (url, data) => {
      return new Promise((resolve, reject) => {
        wx.request({
          url,
          data,
          method,
          header: {
            'Content-Type': 'application/json'
          },
          success: function(res) {
            resolve(res.data)
          },
          fail: function(err) {
            reject(err)
          }
        });
      })
    }
    export const get = request('GET');
    export const post = request('POST');
    export const put = request('PUT');
    export const del = request('DELETE');
  • 大公告成

坑:别用bluebird

  • 隐蔽的Android真机错误

如果你恰巧使用的是bluebird,又恰巧你只是在模拟器上测试或者你的真机是iOS系统,那么你不会发现异样,但是你用android机器一测试,就会报错了。

  • 错误截图

Android真机promise错误

  • 错误分析
    1. 引入promise库,如bluebird
    2. 用小程序官方的方法写

由于第二个方案的复用性不够好,第一选择肯定是第一个方案,so,我开始找到bluebird引入到项目代码中,如下:

// request.js
var Promise require('bluebird.min');

文件结构如下:

/requests
  /request.js
  /bluebird.min.js

引入进去后,在开发工具上跑起来没问题,但是在手机(安卓)上预览的时候,发现有如下报错:

TypeError: Cannot read property 'createElement' of undefined
...

于是以为是bluebird的引入方式有问题,尝试用import,还是报同样的错误。

import Promise from 'bluebird.min';
  • 错误原因
    通过查看bluebird源码发现,其中有关于对dom操作的代码,导致在小程序中跑不通。
  • 解决方案是:

到这里你就可以回到文章开头的解决方案,老实的使用es-6.promise.min.js

找到一个轻量级、只包含promise异步的核心代码的promise库来引入。
通过小伙伴的帮助,找到es-6.promise.min.js引入进去,终于在手机上跑通了。。。

在手机上运行成功的兴奋之余,题主还用iphone真机测试了一番,发现在iphone上直接引入非轻量级的bluebird.min.js也是可行的,当然引入轻量级的es-6.promise.min.js也是没问题的。
需要注意的是bluebird.min.js有70多kb,es-6.promise.min.js有6kb,对于限制代码不超过1024kb的小程序而言,轻量级能给项目省下更多的空间,且兼容性更强。

来源

微信小程序开发论坛
垂直微信小程序开发交流论坛

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

微信小程序开发思考总结——腾讯“信用卡还款”项目实践-演道网

小程序概述

昨天晚上,微信团队对外宣布,微信小程序开放公测。开发者可登陆微信公众平台申请,开发完成后可以提交审核,公测期间咱不能发布。

我们前一段时间也进行了小程序开发,现在来对之前的开发体验做一个总结。

1. 小程序是什么?

微信小程序是一种介于原生app、和web app的hybrid。通过微信进行加载,实现类似原生app的流畅。相对原生app来说,小程序更加轻量、更新实时、跨平台;相对web app来说,小程序资源离线,体验更流畅。

微信小程序的设计目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生APP体验的服务。

不说那么多了, 先来看看小程序的效果:

看完效果,是不是对开发充满好奇~

2. 小程序的实现机制

小程序的开发是基于微信提供的一套应用框架进行开发的。微信通过封装微信客户端提供的文件系统、网络通信、任务管理、数据安全等基础功能,对上层提供了一套完整的Javascript Api,使得开发者能够非常方便的使用到微信客户端提供的各种基础功能,快速构建一个应用。框架设计如下:

框架提供了自己的视图层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,并在视图层与逻辑层之间通过单向数据绑定进行数据传输,使开发者更加聚焦于数据与逻辑上。

3. 支持的特性

接下来我们来看一下,微信框架具体提供的特性:

wxml:一切皆组件(视图组件)

  • view组件(类似 H5中的div)
  • input组件(type = digit,有带小数点的9宫格键盘)
  • modal弹窗组件 (对应的wxml、效果如下)(该组件已换js 实现wx.showModal())


更多wxml组件,请查看微信公众平台小程序文档

4. 功能API:

  • 支付
  • 微信信息的获取
  • 网络请求
  • 录音
  • 上传/下载文件
  • webSocket
  • 访问相册

更多详细的API,请查看微信公众平台小程序文档

5. 其他:

  • 下发消息通知
  • 简要的统计(pv、uv)

现在我们来具体开发~

小程序的开发流程

一、获取微信小程序的 AppID

二、创建项目

创建项目,需要通过开发者工具来完成。(工具在微信小程序公众平台下载)

三、编写代码

首先我们来看一下小程序的目录结构:

微信对小程序的开发有些规定:

上图左侧3个文件是放在小程序的根目录中,其他文件由开发者自由控制。

  • app.js是小程序的脚本代码,用来监听并处理小程序的生命周期函数、声明全局变量
  • app.json 是对整个小程序的全局配置,配置小程序是由哪些页面组成,配置小程序的窗口背景色等。

  • app.wxss 是整个小程序的公共样式表

其中app.js,app.json是必需的。

小程序页面是由同路径下同名的四个不同后缀文件的组成:

  • .js后缀的文件是脚本文件
  • .json后缀的文件是配置文件
  • .wxss后缀的是样式表文件
  • .wxml后缀的文件是页面结构文件

在H5开发中,我们是通过在页面中,引入对应的css、js,而小程序开发不需要在wxml中引入,它们是通过相同的名称,将页面与逻辑js、样式进行关联匹配。

接下来,我们动手具体开发吧~

框架:小程序内嵌微信框架,不需额外框架

一般来说我们开始开发,都会从框架开始进行设计。而小程序在封装了一个为客户端设计的框架,作为小程序的开发者是基于该框架开发的,目前现有的框架不用考虑,并且也不需要考虑。

现有的框架基本都是建立在window、document对象上。小程序是没有window、document,或者说没有浏览器BOM这个宿主环境。你可以理解为小程序的宿主环境是类似node的宿主环境,而不是浏览器客户端。关于这个环境的设计,感兴趣的童鞋可以参考PWS

模块化:形式上支持CommonJs,加载引用更像ES6

小程序形式支持CommonJS,通过require加载,跟node、seajs类似。但是通过require加载的是引用的赋值,而不是CommonJS中的值的缓存。

小程序的模块书写:

小程序的模块运行(类似node,框架会自动添加外层define):

模块化:UI组件设计

在开发时,与视图相关的组件模块化时,我们可能需要注意一下。例如弹框,在H5中,我们一般是将其封装成一个模块组件,这样可以复用。在小程序中,视图只能在wxml中,不能动态生成。

首先,我们看一下微信的弹窗的视图组件modal,微信之前给的api是这样的(该组件微信已经使用其他方式实现, 这里用它来描述问题):

 

看到这样,你是否有联想,如果一个页面需要使用100个弹框,开发者需要创建100wxml组件,及注册对应的100个确定按钮的事件,100个取消按钮的事件。这显然是不合理的。

能不能在框架上进行封装成一个通用组件,开发者只需传入对应的事件句柄即可?后期微信可能会考虑封装吧~

NO~!

为什么呢?我们从框架组件设计来看,框架本身采用面向状态的编程方式,组件部分类似redux的设计(实际不是redux实现的)

组件的View在action操作后,只能通过action的业务处理进行更新View。而框架是单向数据绑定,无法自动更新。

对于这一类View组件自带action的,建议进行必要再封装。封装可以考虑aop的方式动态的注册&卸载

1. 定义组件的通用模版

2. aop方式封装组件的逻辑

1)组件的默认配置:

2)组件的封装实现

3. 组件的使用:

1)在页面wxml中引入组件的模版

2)在页面js中,随时不限次数使用弹框

目前该组件微信已经封装(api:wx.showModal()调用弹框),不过action不能自动更新的特性依旧存在,开发者如果需要自定义其他带有交互的UI组件时,依然会遇见以上问题,可以参考以上解决思路。

具体页面开发

对于业务页面的开发,可以将页面视为一个页面组件。在这个页面组件,完成了以下工作:

  1. 负责初始化组件state(微信)
  2. 负责组合子view组件形成页面效果(开发者)
  3. 确定js 与view 匹配的数据(开发者)
  4. 负责注册业务逻辑对象提供的业务逻辑方法(开发者)
  5. 负责管理业务逻辑对象(开发者)

1) index.wxml

2) index.js

页面wxml与页面js的通信如下(简化了微信框架的工作)

在页面开发我们需要注意的有:

  • index.js中的data数据只读

页面js中,data数据是需要约定为只读。框架是单向数据绑定,修改data中的数据不会自动更新View;更新view,需要使用setData()方法。setData()更新View时,与data中的数据进行Diff比较,不同才会更新。这样如果直接修改data,很容易造成data中的数据与View不一致。

  • setData单次设置的数据不能超过1024kB,需要避免一次设置过多的数据。
  • template,这些模版具有自己独立的作用域。
  • 支持ES6中的…展开模块数据。

{{…cardInfo}},模版中的数据{{card_name}}、{{bank_simple_name}},对应data中的数据就是data:{cardInfo:{card_name“”,bank_simple_name:“”}},方便了对子view的控制。

这样就完成了页面级的开发~~YES!

小程序与H5的区别

在具体写代码,小程序与H5的开发有什么区别呢?

javascript:

(一)限制:

  • 通过传入字符串来执行代码的能力都禁用了

出于安全考虑,凡是通过传入字符串来执行代码的能力都禁用了。具体被禁掉的原生功能有:new Function、eval、Generator。这是同时也比较有效的避免了类似H5 中xss的问题。

禁掉的这些功能,对我们开发来说影响比较显著的应该是字符串转json,以往我们都是通过new Function、eval来处理后台cgi的返回。(移动端一般封装在zepto之类的框架中),小程序开发需要改变一下具体实现。

  • 与浏览器BOM相关的api都是没有的。

在这些BOM中,对开发影响最大的应该是没有cookie。因为其他功能例如storage,小程序有类似的处理方法。而cookie在web开发中是与后台登录相关的。

小程序中是没有Cookie的,为了兼容目前大部分web app 的登录管理是使用cookie的。小程序在请求发送时,客户端可以动态的给请求设置Header发送报文的cookie。

注意一下cookie本身不能在客户端进行读写。

因为没有cookie,H5中的csrf问题理论上是根本解决了。小程序是否存在其他客户端安全问题,需要技术、时间来检验~

(二) 优化

  • 登录:

H5中,通过微信授权一般采用url重定向的方式获取code;在小程序中,通过wx.login获取code,这样避免了之前登录重定向的问题。

  • storage:

小程序用storage替代了H5中的localstorage、sessionstorage。storage对每个小程序的大小是10M,支持同步和异步。

  • 微信支付路径不再受限

(三) 不便

1)每个页面是需要手动在app.json中进行注册。如果没有注册,是不执行该页面的。
2)打开的页面有5个的限制,在开发时需要主要控制打开页面的数量

wxss:

  • wxss 不再支持媒体查询
  • 增加了app的flex布局
  • 引入rpx的概念

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

  • wxss中,不能使用背景图片。这跟框架的设计思想认为一切皆组件有关
  • wxss动画不支持(目前)
  • 不支持多层选择器.classA {} – 支持; .classA  .classB {} – 不支持 (api说明表示只支持单层选择器,重构测试有时多层是支持的)

四、调试

开发完成后,我们进行调试查看效果,跟移动开发差不多,增加了手机端的log。

开发工具调试:

手机客户端:点击右上角开启调试

五、构建

小程序是采用类似node的本地加载模块,不需考虑seajs中的模块合并,只需要uglify即可。当然如果你采用ES6开发,那还是需要bebal一下。

六、测试环境

具体微信还在调整中…

七、发布

在开发工具中,进行全量提交,通过微信审核后,在微信小程序平台进行最后发布。文件发布加载的流程如下:

八、版本更新

小程序的更新是在启动时进行更新的,首先与客户端版本进行对比,是否有新的版本,如果有新版本,小程序更新,再运行;否则,直接使用本地资源运行。

这样小程序的整体开发发布就Over了~

个人对小程序产品的看法:

优势:

  1. 低门槛下载,作为微信的一环,可以通过微信直接进入,即可使用。几乎可以认为是网页,没有下载门槛。
  2. 跨平台,微信客户端底层封装,支持小程序跨平台
  3. 开发成本低,通过之前的开发对比,小程序的开发比web app 的开发成本还低,并且前端的资源存放、发布运维都集成在微信中(如果和腾讯云功能合加,纯属联想~~ 呵呵)
  4. 页面仿原生,体验更流畅
  5. 小程序可以使用微信的支付功能

局限:

  1. 开发基于微信框架,部分功能受限,不支持现有的其他第三方插件;
  2. 小程序页面只能同时打开5个,如果交互流程较长难以支持;
  3. 小程序包大小限制为1M(目前),所有只适合轻量级

关于微信框架api 的具体内容,请参考

https://mp.weixin.qq.com/debug/wxadoc/dev/index.html

建议在开发小程序时不要以web app的开发思维去思考~

结语

从8月开始加入该项目的内测开发,期间经过了几次地动山摇的调整,及不断的痛苦的迭代,所幸的是框架、开发工具已经趋于完善稳定。期待小程序的正式亮相~~

如果您觉得我们的内容还不错,就请扫描二维码赞赏作者并转发到朋友圈,和小伙伴一起分享吧~


本文系腾讯Bugly独家内容,转载请在文章开头显眼处注明作者和出处“腾讯Bugly(http://bugly.qq.com)”

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

微信小程序-认识 MINA 框架-新手教程2

 

MINA(MINA IS NOT APP) 是在微信中开发小程序的框架,其目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生体验的服务——官方文档。

什么是开发框架?首先,大家不要被“框架”吓到。在程序开发领域,“框架”这个概念可以这么理解:一组便于开发某类特定程序的工具的集合。具体到 MINA 这个框架,MINA 其实就是”一组便于开发微信小程序的工具的集合“,而这个集合到底哪些工具组成,正是我们这一节课要讲到的内容。
手机 APP 的构成在深入讲解技术细节之前,大家不妨思考一下,我们日常使用的手机 APP 的基本构成单位是什么。

结论:手机 APP 由三部分构成:数据逻辑,页面(数据呈现)以及(页面间的)导航。
比如我们拿新浪微博 APP 举例:
数据逻辑:我关注的人,我的粉丝,微博用户所发的微博,以及这些微博数据的组织,比如按关注人分组等等这类跟数据相关的部分。
页面:对数据的呈现,比如我关注的人的微博列表,某个用户的微博主页,长微博文章页等等。
导航:页面间的跳转。比如从微博列表页面可以进入到微博详情页,点击微博发帖人头像可以进入个人主页等等。
微信小程序的构成微信小程序主要由两部分构成:

  • 响应的数据绑定(数据逻辑 + 页面)
  • 页面管理(导航)

稍有不同的是,微信的 MINA 框架把数据逻辑和页面这两部分合起来组成了“响应的数据绑定系统”。顾名思义,这个系统实现了数据和页面的绑定,也就是说,当数据变动的时候,渲染这部分数据的页面也会随之变化。另外,“页面”这个概念也常常被称做“视图”,我们后续课程可能会交叉使用这两个概念,但指代的都是同一个东西。下面让我们深入探讨一下微信小程序的这两个组成部分。
1. 响应的数据绑定

响应的数据绑定系统包含了数据逻辑和页面两个部分。
首先,对于页面部分,MINA 框架提供了自己的视图层描述语言 WXML 和 WXSS。
其中 WXML 跟 HTML 语言(一种用于定义网页的标记语言)很像,用于定义视图结构。那么什么是视图结构呢,举个例子,比如我想做一个页面,这个页面的上半部分要放一张图片,然后图片下面要放一个按钮,这种相对结构关系就是用 WXML 语言来描述和定义的。这类文件均以 .wxml 为后缀名。
而 WXSS 则跟 CSS(一种用于给 HTML 增加样式的语言) 很像,用于定义视图中元素的样式。比如上述的页面,如果我们想要让那张图片有一个 5px 宽的黑色的边(border),或者下面的按钮你想换成绿色并给一个 2px 的圆角(border-radius),那么这类样式相关的定义就是通过 WXSS 定义的。这类文件均以 .wxss 为后缀名。WXSS 其实是 CSS 的一个超集,也就是说 CSS 支持的功能它都有,另外它还实现了一些 CSS 没有的功能,详细细节我们将在后续的课程中探讨。
对于数据逻辑部分,MINA 提供了基于 JavaScript 的逻辑层框架,并在视图层和逻辑层之间提供了数据传输和事件系统。数据逻辑部分的代码都是以 JavaScript 写的,后缀名为 .js。
2. 页面管理

MINA管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据,方法,生命周期函数注册进MINA中,其他的一切复杂的操作都交由MINA处理。——官方文档

所谓的页面管理,指的就是下面这两个部分:

  • 页面间的切换
  • 页面生命周期

第一部分:页面间的切换。这个很好理解,就是页面间的转换,比如点击一个按钮跳到另一个页面,发现某种数据变化之后跳转到另一个页面等等。
第二部分:页面的生命周期,我们可以这么理解,一个页面从开始出现,到渲染完成,及至最后的消失,这整个过程就是页面的生命周期。MINA 的生命周期机制,会在页面整个生命历程的不同阶段触发特定的事件,通过响应这类事件,我们就可以实现丰富的页面功能。比如,我做了一个微信抽奖页面,我想在用户打开这个页面之后给服务器发送一个请求,用于统计打开这个页面的人数,这个时候我们就可以通过自定义这个页面的 onLoad 事件(也叫监听 onLoad 事件),来完成相应的功能。再比如,当用户关掉某个页面的时候我想要给服务器发另一个请求,用于统计离开人数。这时候我们就可以通过 onHide 事件来完成这个功能的接入。
官方示例理论知识讲完了,让我们看一下官方的示例:
首先下载官方开发工具:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html
安装并打开开发者工具,扫码登录,然后点击➕ 加号新建项目,其中 APPID 可以选择无 APPID,然后填写项目名称,项目目录选择一个空的文件夹,勾选“是否需要创建 quick start 项目”,最后点击“添加项目”就会创建官方的示例项目。

 

官方示例项目运行截图:

 

这个示例项目由两个页面构成: index 页,显示当前用户的头像和昵称,并在最下面显示一行 Hello World。点击 index 页的用户头像和昵称部分,会跳到 logs 页,这个页面会显示小程序每次被打开的时间戳列表。logs 页面效果如下图所示:

 

项目代码结构如下图所示

 

最外层的三个名为 app 的文件是整个项目的入口,配置以及样式文件。
其中, app.js是小程序的脚本代码。

我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。调用框架提供的丰富的 API,如本例的同步存储及同步读取本地数据。

//app.jsApp({  onLaunch: function () {    //调用API从本地缓存中获取数据    var logs = wx.getStorageSync(‘logs’) || []    logs.unshift(Date.now())    wx.setStorageSync(‘logs’, logs)  },  getUserInfo:function(cb){    var that = this;    if(this.globalData.userInfo){      typeof cb == “function” && cb(this.globalData.userInfo)    }else{      //调用登录接口      wx.login({        success: function () {          wx.getUserInfo({            success: function (res) {              that.globalData.userInfo = res.userInfo;              typeof cb == “function” && cb(that.globalData.userInfo)            }          })        }      });    }  },  globalData:{    userInfo:null  }})

首先在代码的最下面我们看到,实例化 App 的时候创建了一个名为 globalData 的全局变量。
得益于 MINA 框架的生命周期机制,app.js 监听了程序启动之后的 onLaunch 事件,并在响应的代码中做了一件事情,那就是把当前时间通过 setStorageSync 接口存储起来,在下面的 logs 页面我们将看到对这部分数据的使用。
getUserInfo 则是自定义的一个方法,这个方法的功能是获取微信用户的基本信息,并把它存在全局变量 globalData 中去。

app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,配置默认标题。

{  “pages”:[    “pages/index/index”,    “pages/logs/logs”  ],  “window”:{    “backgroundTextStyle”:”light”,    “navigationBarBackgroundColor”: “#fff”,    “navigationBarTitleText”: “WeChat”,    “navigationBarTextStyle”:”black”  }}

app.wxss 是整个小程序的公共样式表,也可以理解为基础样式。我们可以在页面组件的 class 属性上直接使用 app.wxss 中声明的样式规则。

/**app.wxss**/.container {  height: 100%;  display: flex;  flex-direction: column;  align-items: center;  justify-content: space-between;  padding: 200rpx 0;  box-sizing: border-box;}

紧接着,utils 文件夹则是公用的工具函数,这个文件包含了一个名为 formatTime 的函数,在接下来的 logs 页面,我们可以看到 logs.js 这个文件对 utils 中的函数的使用。

最后,pages 文件夹包含了项目包含的页面,内部的 index 和 logs 两个文件夹分别表示两个不同的页面。其中每个文件夹内部都有同名的 js,wxml,wxss 文件,分别对应我们之前讲到的数据逻辑部分和视图部分。其中 json 文件是页面的配置文件。
index 页面结构index.wxml 是页面的结构文件,页面使用了 WXML 语法中的 viewimagetext 来搭建页面结构,绑定数据和交互处理函数。
比如 userinfo 这个 view 上就绑定了 tap 事件,事件触发后对应的处理函数则是 bindViewTap。
<!–index.wxml–><view class=”container”>  <view  bindtap=”bindViewTap” class=”userinfo”>    <image class=”userinfo-avatar” src=”{{userInfo.avatarUrl}}” background-size=”cover”></image>    <text class=”userinfo-nickname”>{{userInfo.nickName}}</text>  </view>  <view class=”usermotto”>    <text class=”user-motto”>{{motto}}</text>  </view></view>
index.js 是页面的脚本文件,在这个文件中我们可以监听并处理页面的生命周期函数、获取小程序实例,声明并处理数据,响应页面交互事件等。
//index.js//获取应用实例var app = getApp()Page({  data: {    motto: ‘Hello World’,    userInfo: {}  },  //事件处理函数  bindViewTap: function() {    wx.navigateTo({      url: ‘../logs/logs’    })  },  onLoad: function () {    console.log(‘onLoad’)    var that = this    //调用应用实例的方法获取全局数据    app.getUserInfo(function(userInfo){      //更新数据      that.setData({        userInfo:userInfo      })    })  }})
index.js 做的事情是实例化了一个 Page,也就是一个页面。其中 data 对应的是这个页面所绑定的动态数据。bindViewTap 则是上面 index.wxml 中的所绑定的 tap 事件对应的处理函数。
index.wxss 是页面的样式表:
/index.wxss/.userinfo {  display: flex;  flex-direction: column;  align-items: center;}.userinfo-avatar {  width: 128rpx;  height: 128rpx;  margin: 20rpx;  border-radius: 50%;}.userinfo-nickname {  color: #aaa;}.usermotto {  margin-top: 200px;}
页面的样式表是非必要的。当有页面样式表时,页面的样式表中的样式规则会层叠覆盖 app.wxss 中的样式规则。如果不指定页面的样式表,也可以在页面的结构文件中直接使用 app.wxss 中指定的样式规则。
index.json 是页面的配置文件,页面的配置文件是非必要的。当有页面的配置文件时,配置项在该页面会覆盖 app.json 的 window 中相同的配置项。如果没有指定的页面配置文件,则在该页面直接使用 app.json 中的默认配置。

index 页面最终渲染出来的结构入下图所示:

 

logs 页面的页面结构
<!–logs.wxml–><view class=”container log-list”>     <block wx:for-items=”{{logs}}” wx:for-item=”log”>        <text class=”log-item”>{{index + 1}}. {{log}}</text>    </block></view>
logs 页面使用 block 控制标签来组织代码,在 block 上使用 wx:for-items 绑定 logs 数据,并将 logs 数据循环展开节点。block 和 wx:for-items 的具体用法将在下面的课程中详细介绍。
//logs.jsvar util = require(‘../../utils/util.js’)Page({  data: {    logs: []  },  onLoad: function () {    this.setData({      logs: (wx.getStorageSync(‘logs’) || []).map(function (log) {        return util.formatTime(new Date(log))      })    })  }})
logs 页面最终渲染出来的结构如下图所示:

 

下个课时我们详细讲解微信小程序各个组件的使用。

微信小程序-开发入门-新手教程

开发微信小程序入门前

2016年09月21日晚 微信发不了微信“小程序”的内测版,一时间整个互联网都炸了锅。个大新闻、论坛都在讨论这个事情。
作为互联网的一猿,我们怎能不紧跟时代的脚步。于是第二天上午也对微信发布的“小程序” 进一步的做了相关了解。
很多人问我这是什么?
我一般回答:这是未来。

安装教程

关于使用教程网上已经有非常多的教程了,我在这里也不过多赘述,就简单的介绍一下。
下载地址:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html

 

Mac的安装方式很简单,与普通应用的安装方式一样。
  • 打开下载好用dmg文件
  • 把”微信web开发者工具”拖进Application就算是安装完成了
  • 依次打开”系统便好设置”->”安全与隐私”->”点按锁按钮以进行更改”->选择”任何来源”
  • 根据提示点”是”、”打开” 然后就可以正常的打开应用了
以下是截图:
先用自己的开发者账号扫一下二维码登陆“微信开web发者工具”,登陆成功后会出现一下界面:
(我已经添加过了,所以不用在意这些细节)
点击“添加项目”
这是会提示输入AppId、项目名称、及项目地址, 如下图
什么是AppID?
注意:这里的App ID不是原来开发者帐号的那个App ID。
众所周知,腾讯只给发了200个开发者账号,那像咱们这种没有资格的怎么办?
我们是猿诶,在不影响他人利益的情况下当然选择破解。
题外话:
听说开发者帐号已经吵到300万了,不知道是真的还是假的!
  • 每个用户只能有20个“小程序”
  • “小程序”不能打开第三方应用
  • 每个应用开发完后打包提交给微信进行审核
  • 应用是在微信的服务器的
  • 无法独立出一个独立的应用,只能在微信上使用
然后点”添加项目”就完成了一个项目的创建。

 

网上有一个微信Demo,也不知道是不是微信官方的,里边有大量的例子。
demo下载:demo.zip
这是一个比较全的demo,包含了大部份功能,及微信所开放的api。
点击关闭退出到项目选择页面,然后选择“添加应用”与上面方法相同,注意,“项目路径”选择刚刚所下载的“Demo” 让后启动,就可以体验大量demo了。

写一个hello world!

一般有三个文件:
  • .wxml 相当于html与xml的结合体
  • .js 就是js文件
  • .wxss 某种css吧
需要注意的是目前小应用暂时不支持其他插件比如jQuery啥的。
选择”编辑”选项卡, 打开/pages/index/index.wxml
是不是很熟悉,就是html与xml的结合 先不做修改。然后打开 /pages/index/index.js
在Page函数里的 data 对象里的 motto的值改成Hello Dudulu
bindViewTap 这个方法是绑定的跳转,如果你设置了userInfo的nickName的值,它将会显示在页面正中央,当你点击”nickName”的时候,它路由跳转到/pages/logs/logs.wxml了
改完后回到”调式”选项卡,点击”重启”就可以刷新刚刚所修改的文本了。
运行效果:
这里有一个错误,github上已经有修复改错误的方法了,还需要替换一个文件:
替换目录: /Applications/wechatwebdevtools.app/Contents/Resources/app.nw/app/dist/weapp/appservice/asdebug.js
就可解决以上报错的问题。

已经有大神把微信的官方文档给抓取下来了, 文档地址: http://notedown.cn/weixin/component

尾巴

咱们这种破解方式显然是不行的,我觉得微信可能会封掉这种方法,所以大家赶紧。
我说它是未来,这个大家自己体会,这次我们不能再错过了。
我们一起加油!

 

微信小程序实战——高仿知乎日报(下)

要做微信小程序首先要对htmlcssjs有一定的基础,还有对微信小程序的API也要非常熟悉

我将该教程分为以下三篇
微信小程序日记——高仿知乎日报(上)
微信小程序日记——高仿知乎日报(中)
微信小程序日记——高仿知乎日报(下)

三篇分别讲不同的组件和功能块

这篇这要讲

  • 主题日报
  • 我的收藏
  • 设置
  • 图片修正

主题日报

主题日报的样式跟首页几乎一模一样,区别在多了一行主编区域。不过这个主编区域没有实现什么功能,本来是点击主编的头像跳转到主编的个人首页简介,没有时间安排就不做了,这也是需要解析html的(累),所以和主页共享一个页面根据主题id判断,如果是主题日报,顶部的swiper换成image,同时显示主编信息

主题日报列表需要接受一个具体的主题日报id,根据这个id来请求接口获取主题日报的日报列表。
点击相应的主题触发这个方法,加载数据之后收起侧滑菜单

toThemePage: function( e ) {
    var _this = this;
    _this.setData( { loading: true,themeId:e.currentTarget.dataset.id });
    console.log( 'themeId',  _this.data.themeId );
    requests.getThemeStories( _this.data.themeId, ( data ) => {
    data.background=data.background.replace("pic1","pic3");
    data.background=data.background.replace("pic2","pic3");
    for(var i=0;i<data.editors.length;i++){
      data.editors[i].avatar=data.editors[i].avatar.replace("pic1","pic3");
      data.editors[i].avatar=data.editors[i].avatar.replace("pic2","pic3");
    }
    data=utils.correctData(data);
    _this.setData( {
      pageData: data.stories,
      background: data.background,
      description: data.description,
      editorData: data.editors
    });
    slideDown.call( this );
  }, null, () => {
      _this.setData( { loading: false });
    });
  },

主题日报的请求列表方式和主页的列表方式差不多,由于没有发现分页参数,主题日报的日报列表这部分也没有分页请求。主题日报的日报详情还是跳转到日报详情页面的。

主题日报

收藏页面

收藏页面就是只剩下下面列表项的,所以也是共用了主页的页面,在主题日报的基础上再判断,如果是我的收藏页面就不显示顶部图片和主编信息
点击我的收藏触发以下方法

toCollectPage: function( ) {
    var _this = this;
    _this.setData( { themeId:-1});
    var pageData = wx.getStorageSync('pageData') || []
    console.log(pageData);
    _this.setData({
      themeId:-1,
      pageData:pageData
    })
    slideDown.call( this );
  },

我的收藏

设置页面

本来想做设置页面里列出的功能,但是工作比较忙,还是归入到后边的完善计划吧,现阶段只做了简单的页面布局。

但是还是讲一下自己的思路
– 夜间模式就是改变应用的显示样式,利用到了css,我们可以在page中放置一个顶层的view来包括起所有的wxml元素,当切换主题时给页面顶层元素一个主题控制类。

<view class="light">
    ....
</view>

<view class="night">
    ...
</view>

那怎么实现换肤立即生效呢?一个页面刚启动是会经过onLoad、onShow等,当第二次进来的时候页面的onLoad事件就不会在次触发,而是触发onShow事件,我们可以通过onShow事件来获取存在全局缓存中的主题设置。


onShow: function() {
    var app = getApp();
    this.setData({theme: app.globalData.theme});
}
<view class="{{theme}}">
    ...
</view>
  • 清除缓存功能,当然是把临时文件和localStorage中的数据清空。
clearDataEvent: function() {
    wx.clearStorage(); //清除应用数据
}
  • 应用的无图浏览模式跟主题的思路差不多,就是判断应用缓存中的设置是否是无图模式,如果是就在内容显示的时候加一个判断,根据这个值来判断是否显示图片类型的内容。
onLoad: function() {
    var app = getApp();
    this.setData({imageMode: app.getImageMode()});
}
<view>
    <image wx:if="{{imageMode}}" src="..." />
    <!--或者-->
    <block wx:if="{{imageMode}}">
        <image src="..." />
    </block>
</view>

设置页面

图片修正

如果是用知乎日报提供的图片地址,如:

http://pic1.zhimg.com//9e26ccbadca80e2e94f89d24b7ce6b04.jpg

pic1和pic2的图片都不能正常显示,这应该是微信小程序开发工具的bug,所以我们只能把它们替换成pic3或者pic4,后来发现首页的列表和顶部图片用pic4可以正常显示,主编头像用pic3才能正常显示,详细页面也用pic3,我新建了一个方法来修正主页图片地址,其他那些只要在相应位置replace一下就好

/**
 * 修正图片url,将pic1和pic2改为pic4
 * @param data
 * @returns {*}
 */
function correctData(data){
  if (("top_stories" in data) ){
    var top_stories=data.top_stories;
    for(var i = 0;i < top_stories.length; i++) {
      top_stories[i].image = top_stories[i].image.replace("pic1", "pic4");
      top_stories[i].image = top_stories[i].image.replace("pic2", "pic4");
    }
    data.top_stories=top_stories;
  }
  var stories=data.stories;
  for(var i = 0;i < stories.length; i++) {
    if (("images" in stories[i]) ){
      var s=stories[i].images[0];
      s=s.replace("pic1", "pic4");
      s=s.replace("pic2", "pic4");
      stories[i].images[0] =s;
    }
  }
  data.stories=stories;
  return data;
}

总结

问题

  • 代码结构比较烂,很多地方都没有优化处理,复用率较低,待重构。
  • 页面布局有些不合理,尺寸控制的不够好。
  • 部分wxml没有用模版功能代替重复的渲染工作,达不到复用效果。

闲语

本次编写的小程序用到了蛮多知识点,虽然花费了不少时间,但是一切都是非常的值得。编写的过程中遇到最大的困难就是解析html内容,可以说是绞尽脑汁,哈哈,智商不足啦。很期待能有网友能奉献出更好的解决方法。这个小例子做的比较简陋,很多功能没有完全实现,跟别人的Android和React仿客户端相比,小巫见大巫啦。还得抽空完成后续的更多功能。

到目前为止,小程序已经更新了几次,支持了ES5/ES6转换、下拉刷新事件、上传文件等功能,不过还有很多API还不能在模拟环境下显示效果。自己觉得一直做类似于豆瓣图书和知乎日报等除了网络请求之外没什么特别的地方的应用也不好,需要尝试新的API来扩展自己的视野,后续打算往未使用到的API进行案例制作。不知不觉已经踏出校园准备有4个月了,很怀念以前的学习日子,做过很多案例,但是都没有写日志和保存的习惯。这次写的字数蛮多的,可累死我了。很幸运自己初入工作圈就能碰上小程序风暴,期待它正式公测!

现阶段比较完整的效果动态图

微信小程序之知乎日报效果图

本次示例的源码地址:

https://github.com/oopsguy/WechatSmallApps

http://git.oschina.net/oopsguy/WechatSmallApps

笔者在原作者基础上优化的地址:

https://github.com/jkgeekJack/WechatSmallApps

如果大家喜欢,给个start激励一下我,以后会有更好的作品与大家分享:)

微信小程序实战——高仿知乎日报(中)

要做微信小程序首先要对htmlcssjs有一定的基础,还有对微信小程序的API也要非常熟悉

我将该教程分为以下三篇

微信小程序实战——高仿知乎日报(上)
微信小程序实战——高仿知乎日报(中)
微信小程序实战——高仿知乎日报(下)
三篇分别讲不同的组件和功能块

这篇这要讲

  • 日报详情页
  • 底部工具栏
  • 评论页面

日报详情页

日报的内容也是最难做的,因为接口返回的内容是html…,天呀,是html!小程序肯本就不支持,解析html的过程非常痛苦,因为本人的正则表达式只是几乎为0,解析方案的寻找过程很虐心,经典的jQuery是用不了了,又没有dom,无法用传统的方式解析html。尝试了正则学习,但是也是无法在短时间内掌握,寻找了很多解析库,大多是依赖浏览器api。不过,上天是不会忽视有心人的,哈哈,还是被我找到了解决方案。幸运的我发现了一个用正则编写的和类似与语法分析方法的xml解析库。这个库是一个very good的网友封装的html解析库。详情点击 用Javascript解析html

由于日报详情内容的html部分结构太大,这里只列出了简要的结构,这个结构是通用的(不过不保证知乎会变动结构,要是变动了,之前的解析可能就没用了…心累)

<div class="question">
    <h2 class="question-title">日本的六大财阀现在怎么样了?</h2>
    <div class="answer">
        <div class="meta">
            <img class="avatar" src="http://pic1.zhimg.com/e53a7f35d5b1e27b00aa90a2c1468a8c_is.jpg">
            <span class="author">leon,</span><span class="bio">data analyst</span>
        </div>
        <div class="content">
            <p>&ldquo;财阀&rdquo;在战后统称为 Group(集团),是以银行和传统工业企业为核心的松散集合体,由于历史渊源而有相互持股。</p>
            <p>Group 对于当今日本企业的意义在于:</p>
            <p><strong>MUFG:三菱集团、三和集团(みどり会)</strong></p>
            <p><img class="content-image" src="http://pic1.zhimg.com/70/90c319ac7a7b2723e5b511de954f45bc_b.jpg" alt=""
                /></p>
        </div>
    </div>
    <div class="view-more"><a href="http://www.zhihu.com/question/23907827">查看知乎讨论<span class="js-question-holder"></span></a></div>
</div>

外层的.question是日报中问题答案的显示单位,可能有多个,因此需要循环显示。.question-title是问题的标题,.meta中是作者的信息,img.avatar是用户的头像,span.author是用户的名称,span.bio可能使用户的签名吧。最难解析的是.content中的内容,比较多。但是有个规律就是都是以<p>标签包裹着,获取了.content中的所有p就可以得到所有的段落。之后再解析出段落中的图片。

以下是详情页的内容展示模版

<view style="padding-bottom: 150rpx;">
    <block wx:for="{{news.body}}">
        <view class="article">
            <view class="title" wx:if="{{item.title && item.title != ''}}">
                <text>{{item.title}}</text>
            </view>
            <view class="author-info" wx:if="{{(item.avatar && item.avatar != '') || (item.author && item.author != '') || (item.bio && item.bio != '')}}">
                <image wx:if="{{item.avatar && item.avatar != ''}}" class="avatar" src="{{item.avatar}}"></image>
                <text wx:if="{{item.author && item.author != ''}}" class="author-name">{{item.author}}</text>
                <text wx:if="{{item.bio && item.bio != ''}}" class="author-mark">,{{item.bio}}</text>
            </view>
            <view class="content" wx:if="{{item.content && item.content.length > 0}}">
                <block wx:for="{{item.content}}" wx:for-item="it">
                    <block wx:if="{{it.type == 'p'}}">
                        <text>{{it.value}}</text>
                    </block>
                    <block wx:elif="{{it.type == 'img'}}">
                        <image mode="aspectFill" src="{{it.value}}" data-src="{{it.value}}" bindtap="previewImgEvent" />
                    </block>
                    <block wx:elif="{{it.type == 'pstrong'}}">
                        <text class="strong">{{it.value}}</text>
                    </block>
                    <block wx:elif="{{it.type == 'pem'}}">
                        <text class="em">{{it.value}}</text>
                    </block>
                    <block wx:elif="{{it.type == 'blockquote'}}">
                        <text class="qoute">{{it.value}}</text>
                    </block>
                    <block wx:else>
                        <text>{{it.value}}</text>
                    </block>
                </block>

            </view>

            <view  class="discuss" wx:if="{{item.more && item.more != ''}}">
                <navigator url="{{item.more}}">查看知乎讨论</navigator>
            </view>
        </view>
    </block>
</view>

可以看出模版中的内容展示部分用了蛮多的block加判断语句wx:if wx:elif wx:else。这些都是为了需要根据解析后的内容类型来判断需要展示什么标签和样式。解析后的内容大概格式是这样的:

{
    body: [
       title: '标题',
       author: '作者', 
       bio: '签名', 
       avatar: '头像', 
       more: '更多地址',
       content: [   //内容
            {
                type: 'p',
                value: '普通段落内容'
            },
            {
                type: 'img',
                value: 'http://xxx.xx.xx/1.jpg'
            },
            {
                type: 'pem',
                value: '...'
            },
            ...
       ]
    ],
    ...
}

需要注意的一点是主题日报有时候返回的html内容是经过unicode编码的不能直接显示,里边全是类似&#xxxx;的字符,这需要单独为主题日报的日报详情解析编码,微信小程序是不会解析特殊符号的,我们要手动转换,这里只转了最常用几个。

再点击主题日报中的列表项是,传递一个标记是主题日报的参数theme

//跳转到日报详情页
toDetailPage: function( e ) {
    var id = e.currentTarget.dataset.id;
    wx.navigateTo( {
      url: '../detail/detail?theme=1&id=' + id
    });
},    

然后在Detail.js的onLoad事件中接受参数

//获取列表残过来的参数 id:日报id, theme:是否是主题日报内容(因为主题日报的内容有些需要单独解析)
onLoad: function( options ) {
    var id = options.id;
    var isTheme = options[ 'theme' ];
    this.setData( { id: id, isTheme: isTheme });
},

之后开始请求接口获取日报详情,并根据是否是主题日报进行个性化解析

//加载页面相关数据
function loadData() {
  var _this = this;
  var id = this.data.id;
  var isTheme = this.data.isTheme;
  //获取日报详情内容
  _this.setData( { loading: true });
  requests.getNewsDetail( id, ( data ) => {
    data.body = utils.parseStory( data.body, isTheme );
    _this.setData( { news: data, pageShow: 'block' });
    wx.setNavigationBarTitle( { title: data.title }); //设置标题
  }, null, () => {
    _this.setData( { loading: false });
  });
}

以上传入一个isTheme参数进入解析方法,解析方法根据此参数判断是否需要进行单独的编码解析。

内容解析的库代码比较多,就不贴出了,可以到git上查看。这里给出解析的封装。

var HtmlParser = require( 'htmlParseUtil.js' );

String.prototype.trim = function() {
  return this.replace( /(^s*)|(s*$)/g, '' );
}

String.prototype.isEmpty = function() {
  return this.trim() == '';
}

/**
 * 快捷方法 获取HtmlParser对象
 * @param {string} html html文本
 * @return {object} HtmlParser
 */
function $( html ) {
  return new HtmlParser( html );
}

/**
 * 解析story对象的body部分
 * @param {string} html body的html文本
 * @param {boolean} isDecode 是否需要unicode解析
 * @return {object} 解析后的对象
 */
function parseStory( html, isDecode ) {
  var questionArr = $( html ).tag( 'div' ).attr( 'class', 'question' ).match();
  var stories = [];
  var $story;
  if( questionArr ) {
    for( var i = 0, len = questionArr.length;i < len;i++ ) {
      $story = $( questionArr[ i ] );
      stories.push( {
        title: getArrayContent( $story.tag( 'h2' ).attr( 'class', 'question-title' ).match() ),
        avatar: getArrayContent( getArrayContent( $story.tag( 'div' ).attr( 'class', 'meta' ).match() ).jhe_ma( 'img', 'src' ) ),
        author: getArrayContent( $story.tag( 'span' ).attr( 'class', 'author' ).match() ),
        bio: getArrayContent( $story.tag( 'span' ).attr( 'class', 'bio' ).match() ),
        content: parseStoryContent( $story, isDecode ),
        more: getArrayContent( getArrayContent( $( html ).tag( 'div' ).attr( 'class', 'view-more' ).match() ).jhe_ma( 'a', 'href' ) )
      });
    }
  }
  return stories;
}

/**
 * 解析文章内容
 * @param {string} $story htmlparser对象
 * @param {boolean} isDecode 是否需要unicode解析
 * @returb {object} 文章内容对象
 */
function parseStoryContent( $story, isDecode ) {
  var content = [];
  var ps = $story.tag( 'p' ).match();
  var p, strong, img, blockquote, em;
  if( ps ) {
    for( var i = 0, len = ps.length;i < len;i++ ) {
      p = transferSign(ps[ i ]); //获取<p>的内容 ,并将特殊符号转义
      if( !p || p.isEmpty() )
        continue;

      img = getArrayContent(( p.jhe_ma( 'img', 'src' ) ) );
      strong = getArrayContent( p.jhe_om( 'strong' ) );
      em = getArrayContent( p.jhe_om( 'em' ) );
      blockquote = getArrayContent( p.jhe_om( 'blockquote' ) );

      if( !img.isEmpty() ) { //获取图片
        img=img.replace("pic1","pic3");
        img=img.replace("pic2","pic3");
        content.push( { type: 'img', value: img });
      }
      else if( isOnly( p, strong ) ) { //获取加粗段落<p><strong>...</strong></p>
        strong = decodeHtml( strong, isDecode );
        if( !strong.isEmpty() )
          content.push( { type: 'pstrong', value: strong });
      }
      else if( isOnly( p, em ) ) { //获取强调段落 <p><em>...</em></p>
        em = decodeHtml( em, isDecode );
        if( !em.isEmpty() )
          content.push( { type: 'pem', value: em });
      }
      else if( isOnly( p, blockquote ) ) { //获取引用块 <p><blockquote>...</blockquote></p>
        blockquote = decodeHtml( blockquote, isDecode );
        if( !blockquote.isEmpty() )
          content.push( { type: 'blockquote', value: blockquote });
      }
      else { //其他类型 归类为普通段落 ....太累了 不想解析了T_T
        p = decodeHtml( p, isDecode );
        if( !p.isEmpty() )
          content.push( { type: 'p', value: p });
      }
    }
  }
  return content;
}

/**
 * 取出多余或者难以解析的html并且替换转义符号
 */
function decodeHtml( value, isDecode ) {
  if( !value ) return '';
  value = value.replace( /<[^>]+>/g, '' )
    .replace( /&nbsp;/g, ' ' )
    .replace( /&ldquo;/g, '"' )
    .replace( /&rdquo;/g, '"' ).replace( /&middot;/g, '·' );
  if( isDecode )
    return decodeUnicode( value.replace( /&#/g, 'u' ) );
  return value;

}

/**
 * 解析段落的unicode字符,主题日报中的内容又很多是编码过的
 */
function decodeUnicode( str ) {
  var ret = '';
  var splits = str.split( ';' );
  for( let i = 0;i < splits.length;i++ ) {
    ret += spliteDecode( splits[ i ] );
  }
  return ret;
};

/**
 * 解析单个unidecode字符
 */
function spliteDecode( value ) {
  var target = value.match( /ud+/g );
  if( target && target.length > 0 ) { //解析类似  "7.1 u20998" 参杂其他字符
    target = target[ 0 ];
    var temp = value.replace( target, '{{@}}' );
    target = target.replace( 'u', '' );
    target = String.fromCharCode( parseInt( target ) );
    return temp.replace( "{{@}}", target );
  } else {
    // value = value.replace( 'u', '' );
    // return String.fromCharCode( parseInt( value, '10' ) )
    return value;
  }
}

/**
 * 获取数组中的内容(一般为第一个元素)
 * @param {array} arr 内容数组
 * @return {string} 内容
 */
function getArrayContent( arr ) {
  if( !arr || arr.length == 0 ) return '';
  return arr[ 0 ];
}

function isOnly( src, target ) {
  return src.trim() == target;
}

module.exports = {
  parseStory: parseStory
}

/**
 * 将转义字符转为实体
 * @param data
 * @returns {*}
 */
function transferSign(data){
  data=data.replace(/&ndash;/g,"–");
  data=data.replace(/&mdash;/g,"—");
  data=data.replace(/&hellip;/g,"…");
  data=data.replace(/&bull;/g,"•");
  data=data.replace(/&rsquo;/g,"’");
  data=data.replace(/&ndash;/g,"–");
  return data;
}

代码的解析过程比较繁杂,大家可以根据返回的html结构和参照解析库的作者写的文章来解读。

详细页面1

详细页面2

底部工具栏

一般资讯APP的详情页都有一个底部的工具栏用于操作分享、收藏、评论和点赞等等。为了更好地锻炼动手能力,自己也做了一个底部工具栏,虽然官方的APP并没有这个东西。前面介绍到的获取额外信息API在这里就被使用了。本来自己是想把推荐人数和评论数显示在底部的图片右上角,但是由于本人的设计问题,底部的字号已经是很小了,显示数量的地方的字号又不能再小了,这样看起来数字显示的地方和图标的大小几乎一样,很是别扭,所以就不现实数字了。

<view class="toolbar">
        <view class="inner">
            <view class="item" bindtap="showModalEvent"><image src="../../images/share.png" /></view>
            <view class="item" bindtap="reloadEvent"><image src="../../images/refresh.png" /></view>
            <view class="item" bindtap="collectOrNot" wx:if="{{isCollect}}"><image src="../../images/star_yellow.png" /></view>
            <view class="item" bindtap="collectOrNot" wx:else><image src="../../images/star.png" /></view>
            <view class="item" data-id="{{id}}" bindtap="toCommentPage"><image src="../../images/insert_comment.png" />
                <view class="tip"></view>
            </view>
            <view class="item">
                <image src="../../images/thumb_up.png" />
            </view>
        </view>
    </view>

底部有分享、收藏、评论和点赞按钮,收藏功能主要用到数据的储存,存在就去掉后储存,不存在就添加后储存

collectOrNot: function() {
    var pageData = wx.getStorageSync('pageData') || []
    console.log(pageData);
    if (this.data.isCollect){
      for(var i=0;i<pageData.length;i++){
        if (pageData[i].id==this.data.id){
          pageData.splice(i,1);
          this.setData( { isCollect: false });
          break;
        }
      }
    }else {
      var images=new Array(this.data.news.image);
      var item ={id:this.data.id,title:this.data.news.title,images:images};
      console.log(item);
      pageData.unshift(item);
      this.setData( { isCollect: true });
    }
    try {
      wx.setStorageSync('pageData',pageData);
    } catch (e) {
    }
    console.log(pageData);
  }

分享肯定是做不了啦,哈哈,但是效果还是需要有的,就一个modal弹窗,显示各类社交应用的图标就行啦。

<modal class="modal" confirm-text="取消" no-cancel hidden="{{modalHidden}}" bindconfirm="hideModalEvent">
    <view class="share-list">
        <view class="item"><image src="../../images/share_qq.png" /></view>
        <view class="item"><image src="../../images/share_pengyouquan.png" /></view>
        <view class="item"><image src="../../images/share_qzone.png" /></view>
    </view>
    <view class="share-list" style="margin-top: 20rpx">
        <view class="item"><image src="../../images/share_weibo.png" /></view>
        <view class="item"><image src="../../images/share_alipay.png" /></view>
        <view class="item"><image src="../../images/share_plus.png" /></view>
    </view>
</modal>

model的隐藏和显示都是通过hidden属性来控制。

底部工具栏中还有一个按钮是刷新,其实就是一个重新调用接口请求数据的过程而已。

//重新加载数据
reloadEvent: function() {
    loadData.call( this );
},

内容分享

评论页面

评论页面蛮简单的,就是展示评论列表,但是要展示两部分,一部分是长评,另一部分是短评。长评跟短评的布局都是通用的。进入到评论页面时,如果长评有数据,则先加载长评,短评需要用户点击短评标题才加载,否则就直接加载短评。这需要上一个详情页面中传递日报的额外信息过来(即长评数量和短评数量)。

之前已经在日报详情页面中,顺便加载了额外的信息

//请求日报额外信息(主要是评论数和推荐人数)
requests.getStoryExtraInfo( id, ( data ) => {
    _this.setData( { extraInfo: data });
});

在跳转到评论页面的时候顺便传递评论数量,这样我们就不用在评论页面在请求一次额外信息了。

//跳转到评论页面
toCommentPage: function( e ) {
    var storyId = e.currentTarget.dataset.id;
    var longCommentCount = this.data.extraInfo ? this.data.extraInfo.long_comments : 0; //长评数目
    var shortCommentCount = this.data.extraInfo ? this.data.extraInfo.short_comments : 0; //短评数目
    //跳转到评论页面,并传递评论数目信息
    wx.navigateTo( {
      url: '../comment/comment?lcount=' + longCommentCount + '&scount=' + shortCommentCount + '&id=' + storyId
    });
}

评论页面接受参数

//获取传递过来的日报id 和 评论数目
onLoad: function( options ) {
    var storyId = options[ 'id' ];
    var longCommentCount = parseInt( options[ 'lcount' ] );
    var shortCommentCount = parseInt( options[ 'scount' ] );
    this.setData( { storyId: storyId, longCommentCount: longCommentCount, shortCommentCount: shortCommentCount });
},

进入页面立刻加载数据

//加载长评列表
onReady: function() {
    var storyId = this.data.storyId;
    var _this = this;
    this.setData( { loading: true, toastHidden: true });

    //如果长评数量大于0,则加载长评,否则加载短评
    if( this.data.longCommentCount > 0 ) {
      requests.getStoryLongComments( storyId, ( data ) => {
        console.log( data );
        _this.setData( { longCommentData: data.comments });
      }, () => {
        _this.setData( { toastHidden: false, toastMsg: '请求失败' });
      }, () => {
        _this.setData( { loading: false });
      });
    } else {
      loadShortComments.call( this );
    }
}


/**
 * 加载短评列表
 */
function loadShortComments() {
  var storyId = this.data.storyId;
  var _this = this;
  this.setData( { loading: true, toastHidden: true });
  requests.getStoryShortComments( storyId, ( data ) => {
    _this.setData( { shortCommentData: data.comments });
  }, () => {
    _this.setData( { toastHidden: false, toastMsg: '请求失败' });
  }, () => {
    _this.setData( { loading: false });
  });
}

评论页面的展示也是非常的简单,一下给出长评模版,短评也是一样的,里面的点赞按钮功能木有实现哦。

<view class="headline">
    <text>{{longCommentCount}}条长评</text>
</view>

<view class="common-list">
    <block wx:for="{{longCommentData}}">
        <view class="list-item has-img" data-id="{{item.id}}">
            <view class="content">
                <view class="header">
                    <text class="title">{{item.author}}</text>
                    <image class="vote" src="../../images/thumb_up.png" />
                </view>
                <text class="body">{{item.content}}</text>
                <text class="bottom">{{item.time}}</text>
            </view>
            <image src="{{item.avatar}}" class="cover" />
        </view>  
    </block>
</view>

评论页面

微信小程序实战——高仿知乎日报(上)

要做微信小程序首先要对htmlcssjs有一定的基础,还有对微信小程序的API也要非常熟悉

我将该教程分为以下三篇
微信小程序实战——高仿知乎日报(上)
微信小程序实战——高仿知乎日报(中)
微信小程序实战——高仿知乎日报(下)

三篇分别讲不同的组件和功能块

这篇这要讲

  • API分析
  • 启动页
  • 轮播图
  • 日报列表
  • 浮动按钮
  • 侧滑菜单

API分析

以下是使用到的具体API,更加详细参数和返回结构可参照网上网友分享的 知乎日报-API-分析 ,在此就不做再次分析了。

启动界面图片

http://news-at.zhihu.com/api/4/start-image/{size}

参数 说明
size 图片尺寸,格式:宽*高。例如: 768*1024

获取刚进入应用时的显示封面,可以根据传递的尺寸参数来获取适配用户屏幕的封面。

获取最新日报

http://news-at.zhihu.com/api/4/news/latest

返回的数据用于日报的首页列表,首页的结构有上下部分,上部分是图片滑动模块,用于展示热门日报,下部分是首页日报列表,以上接口返回的数据有热门日报和首页日报

获取日报详细

http://news-at.zhihu.com/api/4/news/{id}

参数 说明
id 日报id

在点击日报列表也的日报项时,需要跳转到日报详情页展示日报的具体信息,这个接口用来获取日报的展示封面和具体内容。

历史日报

http://news.at.zhihu.com/api/4/news/before/{date}

参数 说明
date 年月日格式时间yyyyMMdd,例如:20150903、20161202

这个接口也是用与首页列表的日报展示,但是不同的是此接口需要传一个日期参数,如20150804格式。获取最新日报接口只能获取当天的日报列表,如果需要获取前天或者更久之前的日报,则需要这个接口单独获取。

日报额外信息

http://news-at.zhihu.com/api/4/story-extra/{id}

参数 说明
id 日报id

在日报详情页面中,不仅要展示日报的内容,好需要额外获取此日报的评论数目和推荐人数等额外信息。

日报长评

http://news-at.zhihu.com/api/4/story/{id}/long-comments

参数 说明
id 日报id

日报的评论页面展示长评用到的接口(没有找到分页参数,分页没有做)

日报短评

http://news-at.zhihu.com/api/4/story/{id}/short-comments

参数 说明
id 日报id

日报的评论页面展示段评用到的接口(没有找到分页参数,分页没有做)

主题日报栏目列表

http://news-at.zhihu.com/api/4/themes

主页的侧边栏显示有主题日报的列表,需要通过这个接口获取主题日报栏目列表

主题日报具体内容列表

http://news-at.zhihu.com/api/4/theme/{themeId}

参数 说明
themeId 主题日报栏目id

在主页侧栏点击主题日报进入主题日报的内容页,需要展示此主题日报下的日报列表。

启动页

作为一个仿制知乎日报的伪APP,高大上的启动封面是必须的,哈哈。启动页面很简单,请求一个应用启动封面接口,获取封面路径和版权信息。当进入页面,在onLoad事件中获取屏幕的宽和高来请求适合尺寸的图片,在onReady中请求加载图片,在请求成果之后,延迟2s进入首页,防止页面一闪而过。

onLoad: function( options ) {
    var _this = this;
    wx.getSystemInfo( {
      success: function( res ) {
        _this.setData( {
          screenHeight: res.windowHeight,
          screenWidth: res.windowWidth,
        });
      }
    });
},

onReady: function() {
    var _this = this;
    var size = this.data.screenWidth + '*' + this.data.screenHeight;
    requests.getSplashCover( size, ( data ) => {
      _this.setData( { splash: data });
    }, null, () => {
      toIndexPage.call(_this);
    });
}

/**
 * 跳转到首页
 */
function toIndexPage() {
  setTimeout( function() {
    wx.redirectTo( {
      url: '../index/index'
    });
  }, 2000 );
}

Splash启动页面

轮播图

首页顶部需要用到轮播图来展示热门日报,小程序中的Swipe组件可以实现。

<swiper class="index-swiper" indicator-dots="true" interval="10000">
    <block wx:for="{{sliderData}}">
        <swiper-item data-id="{{item.id}}" bindtap="toDetailPage">
            <image mode="aspectFill" src="{{item.image}}" style="width:100%" />
            <view class="mask"></view>
            <view class="desc"><text>{{item.title}}</text></view>
        </swiper-item>
    </block>
</swiper>

所有的内容都必须要在swiper-item标签中,因为我们的图片不止有一张,而是有多个热门日报信息,需要用循环来展示数据。这里需要指定的是image里的属性mode设置为aspectFill是为了适应组件的宽度,这需要牺牲他的高度,即有可能裁剪,但这是最好的展示效果。toDetailPage是点击事件,触发跳转到日报详情页。在跳转到日报详情页需要附带日报的id过去,我们在循环列表的时候把当前日报的id存到标签的data中,用data-id标识,这有点类似与html5中的data-*API。当在这个标签上发生点击事件的时候,我们可以通过Event.currentTarget.dataset.id来获取data-id的值。

首页

日报列表

列表的布局大同小异,不过这里的列表涉及到分页,我们可以毫不犹豫地使用scroll-view组件,它的scrolltolower是非常好用的,当组件滚动到底部就会触发这个事件。上次的小豆瓣图书也是使用了这个组件分页。不过这次的分页动画跟上次不一样,而是用一个附带旋转动画的刷新图标,使用官方的动画api来实现旋转。

<view class="refresh-block" wx:if="{{loadingMore}}">
    <image animation="{{refreshAnimation}}" src="../../images/refresh.png"></image>
</view>
  • 1
  • 2
  • 3

代码中有一个显眼的animation属性,这个属性就是用来控制动画的。

/**
 * 旋转上拉加载图标
 */
function updateRefreshIcon() {
  var deg = 360;
  var _this = this;

  var animation = wx.createAnimation( {
    duration: 1000
  });

  var timer = setInterval( function() {
    if( !_this.data.loadingMore )
      clearInterval( timer );
    animation.rotateZ( deg ).step();
    deg += 360;
    _this.setData( {
      refreshAnimation: animation.export()
    })
  }, 1000 );
}

当列表加载数据时,给动画设置一个时长duration,然后按Z轴旋转,即垂直方向旋转rotateZ,每次旋转360度,周期是1000毫秒。

列表的布局跟上次的小豆瓣图书的结构差不多,用到了循环结构wx:for和判断语句wx:ifwx:else来控制不同的展示方向。

<view class="common-list">
    <block wx:for="{{pageData}}">
        <view class="list-item {{item.images[0] ? 'has-img': ''}}" wx:if="{{item.type != 3}}" data-id="{{item.id}}" bindtap="toDetailPage">
            <view class="content">
                <text>{{item.title}}</text>
            </view>
            <image wx:if="{{item.images[0]}}" src="{{item.images[0]}}" class="cover"></image>
        </view>
        <view class="list-spliter" wx:else>
            <text>{{item.title}}</text>
        </view>
    </block>
</view>

class="list-spliter"这块是用来显示日期,列表中的日报只要不是同一天的记录,就在中间插入一条日期显示块。在列表项中有一个三元运算判断输出具体的class{{item.images[0] ? 'has-img': ''}},是因为列表中可能没有图片,因此需要判定当前有没有图片,没有图片就不添加class为has-img来控制带有图片列表项的布局。

浮动按钮

因为小程序中没有侧栏组件,无法做到侧滑手势显示侧栏(本人发现touchstart事件和tap事件有冲突,无法实现出手势侧滑判断,所以没有用侧滑手势,可能是本人理解太浅了,没有发现解决方法,嘿嘿…),浮动按钮的样式参照了Android中的FloatAction经典按钮。可以浮动在界面上,还可以滑动到任意位置,背景为稍微透明。

<view class="float-action" bindtap="ballClickEvent" style="opacity: {{ballOpacity}};bottom:{{ballBottom}}px;right:{{ballRight}}px;" bindtouchmove="ballMoveEvent"> 
</view>
.float-action {
  position: absolute;
  bottom: 20px;
  right: 30px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  box-shadow: 2px 2px 10px #AAA;
  background: #1891D4;
  z-index: 100;
}

按钮的样式随便弄了一下,宽高用了px是因为后面的移动判断需要获取屏幕的宽高信息,这些信息的单位是px。wxml绑定了点击事件和移动事件,点击事件是控制侧栏弹出,滑动事件是按钮移动。

//浮动球移动事件
ballMoveEvent: function( e ) {
    var touchs = e.touches[ 0 ];
    var pageX = touchs.pageX;
    var pageY = touchs.pageY;
    if( pageX < 25 ) return;
    if( pageX > this.data.screenWidth - 25 ) return;
    if( this.data.screenHeight - pageY <= 25 ) return;
    if( pageY <= 25 ) return;
    var x = this.data.screenWidth - pageX - 25;
    var y = this.data.screenHeight - pageY - 25;
    this.setData( {
        ballBottom: y,
        ballRight: x
    });
}

touchmove事件中的会传递一个event参数,通过这个参数可以获取到当前手势滑动到的具体坐标信息e.touches[ 0 ]

侧滑菜单

侧滑菜单是一个经典APP布局方案,小程序中没有提供这个组件,甚是遗憾。不过实现起来也不是很难,但是总感觉有点别扭…

侧滑菜单的样式采用了固定定位的布局position: fixed,默认隐藏与左侧,当点击浮动按钮时弹出,点击遮罩或者侧栏上边的关闭按钮时收回。侧栏的弹出和收回动画采用小程序提供的动画API。

<view class="slide-mask" style="display:{{maskDisplay}}" bindtap="slideCloseEvent"></view>
<view class="slide-menu" style="right: {{slideRight}}px;width: {{slideWidth}}px;height:{{slideHeight}}px;" animation="{{slideAnimation}}">
  <icon type="cancel" size="30" class="close-btn" color="#FFF" bindtap="slideCloseEvent" />
  <scroll-view scroll-y="true" style="height:100%;width:100%">
    <view class="header">
      <view class="userinfo">
        <image src="../../images/avatar.png" class="avatar"></image>
        <text>Oopsguy</text>
      </view>
      <view class="toolbar">
        <view class="item">
          <image src="../../images/fav.png"></image>
          <text>收藏</text>
        </view>
        <view class="item" bindtap="toSettingPage">
          <image src="../../images/setting.png"></image>
          <text>设置</text>
        </view>
      </view>
    </view>
    <view class="menu-item home">
      <text>首页</text>
    </view>
    <view class="slide-inner">
      <block wx:for="{{themeData}}">
        <view class="menu-item" data-id="{{item.id}}" bindtap="toThemePage">
          <text>{{item.name}}</text>
          <image src="../../images/plus.png"></image>
        </view>
      </block>
    </view>    
  </scroll-view>
</view>
/*slide-menu*/
.slide-mask {
  position: fixed;
  width: 100%;
  top: 0;
  left: 0;
  bottom: 0;
  background: rgba(0, 0, 0, .3);
  z-index: 800;
}
.slide-menu {
  position: fixed;
  top: 0;
  background: #FFF;
  z-index: 900;
}
/*.slide-menu .slide-inner {
  padding: 40rpx;
}*/
.slide-menu .header {
  background: #019DD6;
  height: 200rpx;
  color: #FFF;
  padding: 20rpx 40rpx 0 40rpx;
}

.userinfo {
  height: 80rpx;
  line-height: 80rpx;
  overflow: hidden;
}
.userinfo .avatar {
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  margin-right: 40rpx;
  float: left;
}
.userinfo text {
  float: left;
  font-size: 35rpx;
}
.toolbar {
  height: 100rpx;
  padding-top: 25rpx;
  line-height: 75rpx;
}
.toolbar .item {
  width: 50%;
  display: inline-block;
  overflow: hidden;
  text-align: center
}
.toolbar .item text {
  display: inline-block;
  font-size: 30rpx
}
.toolbar .item image {
  display: inline-block;
  position: relative;
  top: 10rpx;
  margin-right: 10rpx;
  height: 50rpx;
  width: 50rpx;
}

.slide-menu .menu-item {
  position: relative;
  height: 100rpx;
  line-height: 100rpx;
  padding: 0 40rpx;
  font-size: 35rpx;
}
.slide-menu .menu-item:active {
  background: #FAFAFA;
}
.slide-menu .menu-item image {
  position: absolute;
  top: 25rpx;
  right: 40rpx;
  width: 50rpx;
  height: 50rpx;
}
.slide-menu .home {
  color: #019DD6
}

.slide-menu .close-btn {
  position: absolute;
  top: 20rpx;
  right: 40rpx;
  z-index: 1000
}

以上是侧栏的一个简单的布局和样式,包含了侧栏中的用户信息块和主题日报列表。当然这些信息是需要通过js的中网络请求来获取的。侧栏结构上边有一个class为slide-mask的view,这是一个遮罩元素,当侧栏弹出的时候,侧栏后边就有一层轻微透明的黑色遮罩。侧栏的高度和宽度初始是不定的,需要在进入页面的时候,马上获取设备信息来获取屏幕的高度宽度调整侧栏样式。

//获取设备信息,屏幕的高度宽度
onLoad: function() {
    var _this = this;
    wx.getSystemInfo( {
      success: function( res ) {
        _this.setData( {
          screenHeight: res.windowHeight,
          screenWidth: res.windowWidth,
          slideHeight: res.windowHeight,
          slideRight: res.windowWidth,
          slideWidth: res.windowWidth * 0.7
        });
      }
    });
}

宽度我取了屏幕宽度的70%,高度一致。侧栏的弹出收回动画使用内置动画API

//侧栏展开
function slideUp() {
  var animation = wx.createAnimation( {
    duration: 600
  });
  this.setData( { maskDisplay: 'block' });
  animation.translateX( '100%' ).step();
  this.setData( {
    slideAnimation: animation.export()
  });
}

//侧栏关闭
function slideDown() {
  var animation = wx.createAnimation( {
    duration: 800
  });
  animation.translateX( '-100%' ).step();
  this.setData( {
    slideAnimation: animation.export()
  });
  this.setData( { maskDisplay: 'none' });
}

侧栏弹出的时候,遮罩的css属性display设置为block显示,侧栏通过css动画transform来想右侧移动了100%的宽度translateX(100%),侧栏收回时,动画恰好与弹出的相反,其实这些动画最后都会翻译为css3动画属性,这些API只是css3动画的封装。为了点击遮罩收回侧栏,遮罩的tap事件也要绑定slideCloseEvent

//浮动球点击 侧栏展开
ballClickEvent: function() {
    slideUp.call( this );
},

//遮罩点击  侧栏关闭
slideCloseEvent: function() {
    slideDown.call( this );
}

效果图

侧栏菜单

微信小程序官方示例补充之二维数组列表渲染

微信小程序官方示例补充之二维数组列表渲染

微信小程序官方文档主要是一维数组列表渲染的案例:

Page({
  items: [{
    message: 'foo',
  },{
    message: 'bar'
  }]
})
<view wx:for="{{items}}">
  {{index}}: {{item.message}}
</view>

也有一个“静态”的二维数组列表渲染案例:

<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i">
  <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j">
    <view wx:if="{{i <= j}}">
      {{i}} * {{j}} = {{i * j}}
    </view>
  </view>
</view>

但我需要的是动态二维数组的列表渲染。

Page({
  mapData:[
    [{id:11},{id:12}],
    [{id:21},{id:22}],
    [{id:31},{id:32},{id:33}]
  ]
})
<view class="container">
    <view class="map">
        <view wx:for="{{mapData}}">
            <view wx:for="{{item}}">
                {{item.id}}
            </view>
        </view>
    </view>
</view>

PS:如果是修改了js文件,记得要重启应用查看修改效果~一开始我没有这么做,导致没看到效果,以为是代码问题,然后试了一大堆乱七八糟的代码~浪费了不少时间~

微信小程序自定义布局插件:wxgrid

微信小程序自定义布局插件:wxgrid

引言

微信小程序发布,web端的插件基本都用不了。接下来应该会有不少微信小程序插件出现吧……
微信小程序其实是C/S思想,纯web前端开发人员应该不大喜欢它的写法。

前端开发最重要的就是布局的编写,C/S布局最方便的应该就是“表格”布局,参考.NET的WPF里面Grid,我先简单写一个表格布局的插件。

wxgrid插件含有wxgrid.js和wxgrid.wxss,两个文件。

主要函数

//初始化表格,设置几行几列
init(rowsCount,colsCount)
//设置行高(不设置的话,默认40高度)
//传人height,index设置第index+1行的高度
//只传人height设置所有行的高度
setRowsHeight(height,index)
//设置列宽(不设置的话,默认等宽)
//传人width,index设置第index+1列的宽度“权重”
//只传人height设置所有的的宽度“权重”
setColsWidth(width,index)
//将一维数组转换为二维数组,存储在data
wxgrid.data.add(key,arr);

使用示例:

示例1

index.js

var WXGrid = require('../../js/wxgrid.js')
var wxgrid = new WXGrid;
wxgrid.init(4,4);
wxgrid.setRowsHeight(100,2);
wxgrid.setColsWidth(100,2);
var app = getApp()
Page({
  data: {
    wxgrid
  }
})

index.wxml

<view class="wxgrid">
    <view wx:for="{{wxgrid.rows}}" class="wxrow" style="height:{{item.height}}px;line-height:{{item.height}}px;">
        <view wx:for="{{wxgrid.cols}}" class="wxcol" style="width:{{item.width}}%">
            内容
        </view>
    </view>
</view>

index.wxss

@import "../../wxss/wxgrid.wxss";

效果如下:
效果图片

示例2

数据调用

index.js

var WXGrid = require('../../js/wxgrid.js')
var wxgrid = new WXGrid;
wxgrid.init(2,3);
var classifies = [
  {name:"领聘1"},
  {name:"领聘2"},
  {name:"领聘3"},
  {name:"领聘4"},
  {name:"领聘5"},
  {name:"领聘6"}]
wxgrid.data.add("classifies",classifies);   //将一维数组转换为二维数组
var app = getApp()
Page({
  data: {
    wxgrid
  }
})

index.wxml

<view class="wxgrid">
    <view wx:for="{{wxgrid.rows}}" wx:for-index="i" class="wxrow" style="height:{{item.height}}px;line-height:{{item.height}}px;">
        <view wx:for="{{wxgrid.cols}}" wx:for-index="j" class="wxcol" style="width:{{item.width}}%">
            {{wxgrid.data.classifies[i][j].name}}
        </view>
    </view>
</view>

index.wxss

@import "../../wxss/wxgrid.wxss";

效果如下:

美团式分类试图

示例3

index.js

var WXGrid = require('../../js/wxgrid.js')
var wxgrid = new WXGrid;
wxgrid.init(2,4);
var img = "http://pic.qqtn.com/up/2016-9/20169281936395677.png";
var classifies = [
  {name:"领聘1",img},
  {name:"领聘2",img},
  {name:"领聘3",img},
  {name:"领聘4",img},
  {name:"领聘5",img},
  {name:"领聘6",img},
  {name:"领聘7",img},
  {name:"领聘8",img}]
wxgrid.data.add("classifies",classifies);
var app = getApp()
Page({
  data: {
    wxgrid
  }
})

index.wxml

<view class="wxgrid">
    <view wx:for="{{wxgrid.rows}}" wx:for-index="i" class="wxrow">
        <view wx:for="{{wxgrid.cols}}" wx:for-index="j" class="wxcol" style="width:{{item.width}}%;">
            <a class="wxclassify" href="#">
                <image class="wxclassify-img" mode="aspectFit" src="{{wxgrid.data.classifies[i][j].img}}"></image>
                {{wxgrid.data.classifies[i][j].name}}
            </a>
        </view>
    </view>
</view>

效果如下
图文grid布局

插件代码

https://git.coding.net/duangongbang/wxgrid.git

八大排序算法(冒泡,简单选择,直接插入,快速排序,希尔排序,归并排序,堆排序,基数排序)之代码实现(js&php版本) 

前言

从学习数据结构开始就接触各种算法基础,但是自从应付完考试之后就再也没有练习过,当在开发的时候也是什么时候使用什么时候去查一下,现在在学习JavaScript,趁这个时间再把各种基础算法整理一遍,分别以JS和PHP语法的方式编写代码。

冒泡排序

原理:临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位(因为较大的/较小的 总是交换到了右边,当到最后一位时,他肯定是最大的/最小的),然后再从头开始进行两两比较交换,直到倒数第二位时结束
时间复杂度:平均情况:O(n2) 最好情况:O(n) 最坏情况:O(n2)
空间复杂度:O(1)
稳定性:稳定

       //JavaScript语法
       var array = [23,0,32,45,56,75,43,0,34];

       for(var i = 0; i < array.length; i++)
       {
           var isSort = true;
           for(var j = 0; j < array.length - 1 - i; j++)
           {
               if(array[j] > array[j+1])
               {
                   isSort = false;
                   var temp = array[j];
                   array[j] = array[j + 1];
                   array[j + 1] = temp;
               }
           }
           //如果一次交换都没有产生,就说明数据已经是排过序的,可以直接退出。
           if(isSort)
           {
               break;
           }
       }
       console.log(array);    
 <?php
         $array = [23,0,32,45,56,75,43,0,34];
 
         for($i = 0; $i < count($array); $i++)
         {
             $isSort = true;
             for($j = 0; $j < count($array) - 1; $j++)
             {
                 if($array[$j] > $array[$j+1])
                 {
                     $isSort = false;
                     $temp = $array[$j];
                     $array[$j] = $array[$j + 1];
                     $array[$j + 1] = $temp;
                 }
             }
             if($isSort)
             {
                break;
             }
         }
         var_dump($array);
 ?>

简单选择排序

原理:通过n-i次关键字之间的比较,从n-i+1 个记录中选择关键字最小的记录,并和第i(1<=i<=n)个记录交换。简单说就是分成左右两堆,左堆排过序的,右堆未排序的,从未排序的右堆中找出最小的,放到已排序的左边,形成新的两堆,直到最后排序完成。
简单选择排序的性能要略优于冒泡排序
时间复杂度:平均情况:O(n2) 最好情况:O(n) 最坏情况:O(n2)
空间复杂度:O(1)
稳定性:不稳定

//JavaScript
        var array = [23,0,32,45,56,75,43,0,34];

        for(var i = 0; i < array.length - 1; i++)
        {
            var pos = i;
            for(var j = i + 1; j < array.length;j++)
            {
                if(array[j] < array[pos])
                {
                    pos=j;
                }
            }
            var temp=array[i];
            array[i]=array[pos];
            array[pos]=temp;
        }
        console.log(array);
 ```

 ```php
 <?php
         $array = [23,0,32,45,56,75,43,0,34];
         for($i = 0; $i < count($array); $i++)
     {
         $pos = $i;
         for($j = $i + 1;$j < count($array); $j++)
         {
             if($array[$j] < $array[$pos])
             {
                 $pos = $j;
             }
         }
         $temp = $array[$i];
         $array[$i] = $array[$pos];
         $array[$pos] = $temp;
     }
     var_dump($array);
 
 ?>

直接插入排序

原理:将一个记录插入到已排序好的有序表中,从而得到一个新的记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
这个和平时打牌很像,左边是已排好序的,右边是未排序的。从右边第一个时,一个个拿,拿到后插入到左边已排序的正确位置上,直到排完。
比冒泡法和选择排序的性能要更好一些。
时间复杂度:平均情况:O(n2) 最好情况:O(n) 最坏情况:O(n2)
空间复杂度:O(1)
稳定性:稳定

//JavaScript
var array = [23,0,32,45,56,75,43,0,34];
 for(var j = 0;j < array.length;j++) {
     var key = array[j];
     var i = j - 1;
     while (i > -1 && array[i] > key)
     {
         array[i + 1] = array[i];
         i = i - 1;
     }
     array[i + 1] = key;
 }
 console.log(array);
<?php
    //直接插入排序
        $array = [23,0,32,45,56,75,43,0,34];
        for($i = 0; $i < count($array); $i++)
    {
        $key = $array[$i];
        $j= $i - 1;
        while($j > -1 && $array[$j] > $key)
        {
            $array[$j +1] = $array[$j];
            $j = $j - 1;
        }
        $array[$j + 1] = $key;
    }
    var_dump($array);
?> 

快速排序

原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。实际开发中用得最多的
时间复杂度:平均情况:O(nlog2n) 最好情况:O(nlog2n) 最坏情况:O(n2)
空间复杂度:O(nlog2n)
稳定性:不稳定

 //JavaScript 快速排序

 var array = [23,0,32,45,56,75,43,0,34];
 var quickSort = function(arr) {
    if (arr.length <= 1) { return arr; }//检查数组的元素个数,如果小于等于1,就返回。
    var pivotIndex = Math.floor(arr.length / 2);//
    var pivot = arr.splice(pivotIndex,1)[0];//选择"基准"(pivot),并将其与原数组分离,
    var left = [];//定义两个空数组,用来存放一左一右的两个子集
    var right = [];
    for (var i = 0; i < arr.length; i++)//遍历数组,小于"基准"的元素放入左边的子集,大于基准的元素放入右边的子集。
      {
          if (arr[i] < pivot) {
              left.push(arr[i]);
          } else {
              right.push(arr[i]);
          }
      }
 
      return quickSort(left).concat([pivot], quickSort(right));//使用递归不断重复这个过程,就可以得到排序后的数组。
  };
  var newArray=quickSort(array);
  console.log(newArray);
 <?php
                $array = [23,0,32,45,56,75,43,0,34];
         function quick_sort($arr) {
             //先判断是否需要继续进行
             $length = count($arr);
             if($length <= 1) {
                 return $arr;
             }
         
             $base_num = $arr[0];//选择一个标尺  选择第一个元素
 
             //初始化两个数组
             $left_array = array();//小于标尺的
             $right_array = array();//大于标尺的
             for($i=1; $i<$length; $i++) {            //遍历 除了标尺外的所有元素,按照大小关系放入两个数组内
                 if($base_num > $arr[$i]) {
                     //放入左边数组
                     $left_array[] = $arr[$i];
                 } else {
                     //放入右边
                     $right_array[] = $arr[$i];
                 }
             }
             //再分别对 左边 和 右边的数组进行相同的排序处理方式
             //递归调用这个函数,并记录结果
             $left_array = quick_sort($left_array);
             $right_array = quick_sort($right_array);
             //合并左边 标尺 右边
             return array_merge($left_array, array($base_num), $right_array);
         }
                $newArray=quick_sort($array);
                var_dump($newArray);
 ?>

希尔排序

原理:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。。
时间复杂度:平均情况:O(n√n) 最好情况:O(nlog2n) 最坏情况:O(n2)
空间复杂度:O(1)
稳定性:不稳定

javaScript  希尔排序
 var array = [23,0,32,45,56,75,43,0,34];
 var shellSort = function (arr)
 {
     var length=arr.length;
     var h=1;
     while(h<length/3)
     {
         h=3*h+1;//设置间隔
     }
     while(h>=1)
     {
         for(var i=h; i<length; i++)
         {
             for(var j=i; j>=h && arr[j]<arr[j-h]; j-=h)
             {
                 var temp =arr[j-h];
                 arr[j-h]=arr[j];
                 arr[j]=temp;
             }
         }
         h=(h-1)/3;
     }
     return arr;
 }
 var newArray = shellSort(array);
 console.log(newArray);
 <?php
 //希尔排序
         $array = [23,0,32,45,56,75,43,0,34];
         function shellSort($arr)
         {
             $length=count($arr);
             $h=1;
             while($h<$length/3)
             {
                 $h=3*$h+1;//设置间隔
             }
             while($h>=1)
             {
                 for($i=$h; $i<$length; $i++)
                 {
                     for($j=$i; $j>=$h && $arr[$j]<$arr[$j-$h]; $j-=$h)
                     {
                          $temp =$arr[$j-$h];
                          $arr[$j-$h]=$arr[$j];
                          $arr[$j]=$temp;
                     }
                 }
                 $h=($h-1)/3;
             }
             return $arr;
         }
         $newArray = shellSort($array);
         var_dump($newArray)
 ?>

归并排序

原理:假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(不小于n/2的最小整数)个长度为2或1的有序子序列,再两两归并,…如此重复,直至得到一个长度为n的有序序列为止
时间复杂度:平均情况:O(nlog2n) 最好情况:O(nlog2n) 最坏情况:O(nlog2n)
空间复杂度:O(1)
稳定性:稳定

 //JavaScript 归并排序
         function isArray1(arr){
             if(Object.prototype.toString.call(arr) =='[object Array]'){
                 return true;
             }else{
                 return false;
             }
         }
         function merge(left,right){
             var result=[];
             if(!isArray1(left)){
                 left = [left];
             }
             if(!isArray1(right)){
                 right = [right];
             }
             while(left.length > 0&& right.length >0){
                 if(left[0]<right[0]){
                     result.push(left.shift());
                 }else{
                     result.push(right.shift());
                 }
             }
             return result.concat(left).concat(right);
         }
 
         function mergeSort(arr){
             var len=arr.length;
             var lim ,work=[];
             var i,j,k;
             if(len ==1){
                 return arr;
             }
             for(i=0;i<len;i++){
                 work.push(arr[i]);
             }
             work.push([]);
             for(lim=len;lim>1;){//lim为分组长度
                 for(j=0,k=0;k<lim;j++,k=k+2){
                     work[j]=merge(work[k],work[k+1]);
                 }
                 work[j]=[];
                 lim=Math.floor((lim+1)/2);
             }
             return work[0];
         }
         var array = [23,0,32,45,56,75,43,0,34];
         
         console.log(mergeSort(array));
<?php  
      //归并排序
       function mergeSort(&$arr) {
            $len = count($arr);//求得数组长度
         
            mSort($arr, 0, $len-1);
        }
        //实际实现归并排序的程序
        function mSort(&$arr, $left, $right) {
         
            if($left < $right) {
                //说明子序列内存在多余1个的元素,那么需要拆分,分别排序,合并
                //计算拆分的位置,长度/2 去整
                $center = floor(($left+$right) / 2);
                //递归调用对左边进行再次排序:
                mSort($arr, $left, $center);
                //递归调用对右边进行再次排序
                mSort($arr, $center+1, $right);
                //合并排序结果
                mergeArray($arr, $left, $center, $right);
            }
        }

        //将两个有序数组合并成一个有序数组
        function mergeArray(&$arr, $left, $center, $right) {
            //设置两个起始位置标记
            $a_i = $left;
            $b_i = $center+1;
            while($a_i<=$center && $b_i<=$right) {
                //当数组A和数组B都没有越界时
                if($arr[$a_i] < $arr[$b_i]) {
                    $temp[] = $arr[$a_i++];
                } else {
                    $temp[] = $arr[$b_i++];
                }
            }
            //判断 数组A内的元素是否都用完了,没有的话将其全部插入到C数组内:
            while($a_i <= $center) {
                $temp[] = $arr[$a_i++];
            }
            //判断 数组B内的元素是否都用完了,没有的话将其全部插入到C数组内:
            while($b_i <= $right) {
                $temp[] = $arr[$b_i++];
            }
         
            //将$arrC内排序好的部分,写入到$arr内:
            for($i=0, $len=count($temp); $i<$len; $i++) {
                $arr[$left+$i] = $temp[$i];
            }
         
        }

        $arr = array(23,0,32,45,56,75,43,0,34);
        mergeSort($arr);
        var_dump($arr);
?>

堆排序

原理:堆排序就是利用堆进行排序的方法.基本思想是:将待排序的序列构造成一个大顶堆.此时,整个序列的最大值就是堆顶 的根结点.将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值.如此反复执行,便��得到一个有序序列了
时间复杂度:平均情况:O(nlog2n) 最好情况:O(nlog2n) 最坏情况:O(nlog2n)
空间复杂度:O(1)
稳定性:不稳定

//JavaScript  堆排序    
      var array = [23,0,32,45,56,75,43,0,34];
       function heapSort(array)
       {
           for (var i = Math.floor(array.length / 2); i >= 0; i--)
           {
               heapAdjust(array, i, array.length - 1); //将数组array构建成一个大顶堆
           }
           for (i = array.length - 1; i >= 0; i--)
           {
               /*把根节点交换出去*/
               var temp = array[i];
               array[i] = array[0];
               array[0] = temp;
               /*余下的数组继续构建成大顶堆*/
               heapAdjust(array, 0, i - 1);
           }
           return array;
       }

       function heapAdjust(array, start, max)
       {
           var temp = array[start];//temp是根节点的值
           for (var j = 2 * start; j < max; j *= 2)
           {
               if (j < max && array[j] < array[j + 1])
               {  //取得较大孩子的下标
                   ++j;
               }
               if (temp >= array[j])
                   break;
               array[start] = array[j];
               start = j;
           }
           array[start] = temp;
       }
       var newArray = heapSort(array);
       console.log(newArray);
 
<?php
    //堆排序
    function heapSort(&$arr) {
        #初始化大顶堆
        initHeap($arr, 0, count($arr) - 1);
        
        #开始交换首尾节点,并每次减少一个末尾节点再调整堆,直到剩下一个元素
        for($end = count($arr) - 1; $end > 0; $end--) {
            $temp = $arr[0];
            $arr[0] = $arr[$end];
            $arr[$end] = $temp;
            ajustNodes($arr, 0, $end - 1);
        }
    }
    
    #初始化最大堆,从最后一个非叶子节点开始,最后一个非叶子节点编号为 数组长度/2 向下取整
    function initHeap(&$arr) {
        $len = count($arr);
        for($start = floor($len / 2) - 1; $start >= 0; $start--) {
            ajustNodes($arr, $start, $len - 1);
        }
    }
    
    #调整节点
    #@param $arr    待调整数组
    #@param $start    调整的父节点坐标
    #@param $end    待调整数组结束节点坐标
    function ajustNodes(&$arr, $start, $end) {
        $maxInx = $start;
        $len = $end + 1;    #待调整部分长度
        $leftChildInx = ($start + 1) * 2 - 1;    #左孩子坐标
        $rightChildInx = ($start + 1) * 2;    #右孩子坐标
        
        #如果待调整部分有左孩子
        if($leftChildInx + 1 <= $len) {
            #获取最小节点坐标
            if($arr[$maxInx] < $arr[$leftChildInx]) {
                $maxInx = $leftChildInx;
            }
            
            #如果待调整部分有右子节点
            if($rightChildInx + 1 <= $len) {
                if($arr[$maxInx] < $arr[$rightChildInx]) {
                    $maxInx = $rightChildInx;
                }
            }
        }
        
        #交换父节点和最大节点
        if($start != $maxInx) {
            $temp = $arr[$start];
            $arr[$start] = $arr[$maxInx];
            $arr[$maxInx] = $temp;
            
            #如果交换后的子节点还有子节点,继续调整
            if(($maxInx + 1) * 2 <= $len) {
                ajustNodes($arr, $maxInx, $end);
            }
        }
    }
    
    $arr = array(23,0,32,45,56,75,43,0,34);
    heapSort($arr);
    var_dump($arr);
?>        

基数排序

原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
时间复杂度:平均情况:O(d(r+n)) 最好情况:O(d(n+rd)) 最坏情况:O(d(r+n)) r:关键字的基数 d:长度 n:关键字个数
空间复杂度:O(rd+n)
稳定性:稳定

<?php
      #基数排序,此处仅对正整数进行排序,至于负数和浮点数,需要用到补码,各位有兴趣自行研究
      
      #计数排序
      #@param $arr 待排序数组
      #@param $digit_num 根据第几位数进行排序
      function counting_sort(&$arr, $digit_num = false) {
          if ($digit_num !== false) { #如果参数$digit_num不为空,则根据元素的第$digit_num位数进行排序
              for ($i = 0; $i < count($arr); $i++) {
                  $arr_temp[$i] = get_specific_digit($arr[$i], $digit_num);
              } 
          } else {
              $arr_temp = $arr;
          }
  
          $max = max($arr);
          $time_arr = array(); #储存元素出现次数的数组
  
          #初始化出现次数数组
          for ($i = 0; $i <= $max; $i++) {
              $time_arr[$i] = 0;
          }
  
          #统计每个元素出现次数
          for ($i = 0; $i < count($arr_temp); $i++) {
              $time_arr[$arr_temp[$i]]++;
          }
  
          #统计每个元素比其小或相等的元素出现次数
          for ($i = 0; $i < count($time_arr) - 1; $i++) {
              $time_arr[$i + 1] += $time_arr[$i];
          }
  
          #利用出现次数对数组进行排序
          for($i = count($arr) - 1; $i >= 0; $i--) {
              $sorted_arr[$time_arr[$arr_temp[$i]] - 1] = $arr[$i];
              $time_arr[$arr_temp[$i]]--;
          }
  
          $arr = $sorted_arr;
          ksort($arr);    #忽略这次对key排序的效率损耗
      }
  
      #计算某个数的位数
      function get_digit($number) {
         $i = 1;
         while ($number >= pow(10, $i)) {
            $i++;
         }

         return $i;
      }
  
      #获取某个数字的从个位算起的第i位数
      function get_specific_digit($num, $i) {
         if ($num < pow(10, $i - 1)) {
             return 0;
         }
         return floor($num % pow(10, $i) / pow(10, $i - 1));
      }
  
      #基数排序,以计数排序作为子排序过程
      function radix_sort(&$arr) {
          #先求出数组中最大的位数
          $max = max($arr);
          $max_digit = get_digit($max);
  
          for ($i = 1; $i <= $max_digit; $i++) {
              counting_sort($arr, $i);
          }   
      }
  
  
      $arr = array(23,0,32,45,56,75,43,0,34);
      radix_sort($arr);
  
      var_dump($arr);
?>