分类目录归档:web

wordpress启用WP Super Subdomains后子域名登录失效

wordpress启用WP Super Subdomains后子域名登录失效

很开心,终于搞定了WP Super Subdomains。
虽然为了启用它,修改了固定链接,折腾了半小时,又把旧链接跳转到了新链接。
但是除了www.go2live.cn有登录状态,其它几个子域名都是未登录状态。
要编辑文章的时候也很无赖。查看源代码找到了http://www.go2live.cn/?p=id 的短链接,然后把这个id拷贝到编辑页面,就这样勉强过去了。。

这么玩了几天,突然想到,用户不是登录不了,不能发评论了,这可要不得。赶紧百度一下。终于解决了。涉及到两个文件。

1.在wp-config.php加入下面两行代码。

define('COOKIE_DOMAIN', 'go2live.cn');  #这里要顶级域名
define('COOKIEPATH', '/');

2.修改wp-includes/default-constants.php文件

$siteurl = get_site_option( 'siteurl' );#这个东东在后面配置的。按WP Super Subdomains的要求需要是www.go2live.cn
$siteurl = 'go2live.cn'; #重新设置成了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

尾巴

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

 

微信小程序 开发教程 第六课

摘要: 完结篇来了!

大家好!博卡君原计划是能在国庆假期前把小程序的开发教程做完,给大家一套完整、系统的东西,不过由于最近小程序开发工具的拍照组件尚未完善,很多功能还不能顺利实现。我考虑了一下,觉得不如把拍照部分的一些代码展示出来,一来是给大家一个思路,二来也让大家看看目前开发工具存在的一些问题,咱们一起研究研究。

第九章:微信小程序拍照收纳开发以及删除名片等

还是先来看看我们今天的主题——拍照收纳。

拍照收纳分为:上传图片/识别名片、手动填写名片信息两个路径,这里只说下拍照识别,手动填写和前面的编辑名片是同样的页面,大家可以翻翻之前的教程。

这个布局很快,wxml 没多少内容。

拍照收纳的原理是收纳名片功能,步骤是打开微信的拍照 API,同时支持选择本地图片 wx.chooseImage 接口。

取到图片路径需上传图片文件到后台服务器,这里参照文档打印三个回调。

但是我发现在开发者工具测试的时候,没有任何打印信息,后台同样也没有接到我上传过去的图片。

选择一张图片,点击打开后。控制台的 console 没有成功或者失败的回调。

由于尚处内测版本,暂时还不确定是开发者工具上的 bug 还是 API 接口问题,总之这里影响了小程序开发,我已经写邮件给微信做了反馈。

如果图片上传成功,后台取到图片会去调一个识别信息操作,最后把识别到的信息传给我们前台进行渲染,最后把信息保存到名片夹里面,收纳名片流程才算走通。

再补充下名片夹页面的名片删除吧:

每个名片夹后面都带个删除功能,这个功能是通过左滑出现。

点击删除,出现是否确定删除弹框(使用自带的模态框组件)。

确定与取消事件。

由于这里名片分为,线上收纳与线下收纳。故而多了个 if 判断,走的删除 request 不是一个接口,其他都相差不多。这里最重要的是要获取到 cardId,才能知道被删除的是哪张名片。

名片的父元素绑定的全部事件,以及需要用到的一些自定义参数。Id 用在左滑上data-card_id 用在页面跳转与删除上,data-card_type 用在判断线上与线下名片。

cardId 可以在 bindtouchstart 上取到,因为左滑事件是发生 bindtouchstart 事件上。

当然后面需要用到的值还是先定义个 var 存起来。

删除完之后还需重新刷新下首页。

并且提升下用户体验,告诉用户名片删除成功。



最后一提,当列表很多时,我们需要确定点击到的是那个信息。

上面的信息都是 block 循环出来的,bindtap 点击事件,然后就是 id=”{{pms.type}}”,重点就是这个 id 来判断被点击的对象。

操作数据,我们一定要先熟悉好数据的结构,故而在开发前和后台一定要约定好数据结构,熟悉数据结构后,其实开发起来就如庖丁解牛,非常快速(大家开发了小程序后,有没有对 zepoto 与 jQuery 产生反感?哈哈!)

好了,关于拍照组件的相关内容就写到这里吧!这一章内容就算是博卡君教程的完结篇了。虽然写的内容中包含小程序开发工具的问题,不过我还是推荐大家都尝试调用一下拍照相关功能,看看这些问题在你的机器上会不会出现。按照微信的官方文档,我尝试给 weixin_developer@qq.com 那个邮箱发了邮件报告 bug。没想到很快收到了官方回复,确认了这点。希望尽快得到更新版,一起期待吧😄

参考阅读
微信小程序 开发教程 第一课
微信小程序 开发教程 第二课
微信小程序 开发教程 第三课
微信小程序 开发教程 第四课
微信小程序 开发教程 第五课

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

要做微信小程序首先要对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

微信小程序 开发教程 第五课

 

第八章:微信小程序分组开发与左滑功能实现

先来看看今天的整体思路:

进入分组管理页面–>点击新建分组新建

进入到未分组页面基本操作

进入到已建分组里面底部菜单栏操作–>从名片夹中添加进行操作。





理清完基本流程我们开始开发。首先整个布局可以先看下,我再一步步讲。

静态布号局后,我们开始实现新增分组效果,

当然触发它出现的是

显示窗口

点击取消消失

当里面的输入框内容发生改变时 bindinput 事件,大家别使用 bindchange 事件,这里的 bindchange 事件只有当失去焦点时才会被触发。

当输入框事件发生时,确定按钮会变成可以点击状态,为空时,addTeam

样式数据绑定。

里面有表单就使用了 from。

创建分组请求交互。

创建分组完成,这里可能很多疑问,为什么不使用微信提供的模态框组件来实现,而是自己实现,其实我也很懒,非常想使用自带的模态框组件,但是发现在模态框里面提交表单好像有点麻烦,直接自定义了。

接下来创建好的分组支持左滑删除与重命名,今天重点谈下左滑删除的实现方式。

首先需要左滑的是被创建好的分组,未分组是写死的,故而不支持的。

左滑删除使用了两个事件 bindtouchstart 与 bindtouchmove,这里必须绑定它的 id,这个 id 都是不同的,我这使用的用户创建完成后在后台生成的一个 id 号,之后我再 block 出来使用在这里,这个 id 都是唯一的,不能是相同的 id。

样式布局,父元素是绝对定位,子元素删除是相对定位,鉴于动画效果,我这加了 css3 的动画效果,使滑动带点动画效果。

现在我们来看事件构成:

Bindtap 点击之后直接是跳转页面。


左滑从触摸事件开始,当然需使用的参数在外面最好先定义一下:



bindtouchStart 发生后,会执行 bindtouchmove 事件,在这里我们可以判断是否左右滑动:

  • Var dataId = e.currentTarget.id //获取到前面唯一的id;
    If(key){ //用户左滑距离很长时会发生多次左滑事件,这里定义一个开关发生第一次之后就关闭它;
  • Touch是获取触摸点的一些数据,touches 是一个触摸点的数组,每个触摸点包括以下属性:
  • pageX,pageY:距离文档左上角的距离,文档的左上角为原点 ,横向为 X 轴,纵向为 Y 轴;
  • clientX,clientY:距离页面可显示区域(屏幕除去导航条)左上角距离,横向为X轴,纵向为Y轴;
  • screenX,screenY:距离屏幕左上角的距离,屏幕左上角为原点,横向为X轴,纵向为Y轴;

这里使用 clientX,clientY 即可。
我们在 bindtouchstart 时记录到开始的点,在 bindtouchmove 记录到触摸结束的点,如果 X 轴滑动大于 Y 轴的,再如果结束点-开始点小于一个数值(这个数组可以自己设置,鉴于灵敏度,我这设置的很小),右滑也是同理。

判断到左滑与右滑事件后,我们就需要对当前组件绑定数据了,数据从那里来?可以看下我这数组一个来历,以及怎么被左滑事件里面获取到(我这数据是一个数组,需要在 wxml 里面 block 的)。

这时候取到需要的数组,循环出来后,我进行对比,如果数据的 id== 当前被事件操作的 id,那么我给改数组再添加一个 right 向右偏移 15%,否则其他都不偏移,右滑同理,直接全部设置为 0。


最后把数据到绑定到页面上来。

OK,左滑效果实现。

如果出现全部被滚动了,记得在外层加上,X 轴关闭即可。

这时候你点击左滑会出现点透效果,官方文档提供给我们一个 catch,不冒泡点击事件,即可完美解决。

点击删除事件弹出模态框供用户确定是否删除。

这里你点击取消时还要记得把左滑删除按钮去掉可能用户体验会好点。

一定记得在 data({
modalHidden:true
})
不然会出现一加载页面即出现了模态框。

点击未分组按钮进入到未分组名片列表页面。

这个布局完全是 copy 首页过来的,后面只是数据接口有变而已,大家有兴趣可以回过去看下前面的。

点击多选,会出现多选框和下面的一些操作按钮。都是些数据绑定,显示与隐藏类的实现比较简单。

多选我们直接给 checked 设置 Boole 开关。


选择好名片后点击设置分组会弹出新建好的分组列表供用户确定设置到那个分组里面(这里由于后台接口方面还在完善,不继续往下写了)。

用户已建分组点击进去的是点击顶部菜单按钮出现下拉菜单栏,这里直接使用微信提供的组件。

最后点击从名片夹中添加进入到以下页面:

整个布局基本还是和首页面差不多,这里不再多讲,

顶部菜单直接多选绑定数据即可。

牵扯到表单类的基本全都是 from 表单提交事件。
除小部分接口方面欠缺外,写到这里其实分组前端方面效果基本实现。

好了,由于目前小程序开发工具图片上传还有点问题,拍照收纳无法实现,所以博卡君的微信小程序开发教程也先暂告一段落,等开发工具更新稳定后,我还会再追加教程的内容,有机会的话把成品也给大家看看。
微信小程序 开发教程 第一课
微信小程序 开发教程 第二课
微信小程序 开发教程 第三课
微信小程序 开发教程 第四课
微信小程序 开发教程 第六课