未分类

微信小程序入坑: 写一个简化版知乎日报

最近开始入坑微信小程序,发现想要练手也不容易,找免费的基于HTTPS的API就要花点时间。在看了网上一些介绍后,决定采用知乎日报的API,写一个简单版的知乎日报作为入坑练习。本文尝试使用微信小程序写一个小demo,实现知乎日报的3个主要页面以及部分功能。3个页面分别为“首页”、“详情页”和“评论页”。

页面关系

  • index: 首页,展示热闻轮播和热闻列表。点击某条热闻时跳转到详情页面。
  • detail: 详情页,展示某条热闻的内容,可从该页面跳转至相应的评论页面。
  • discussion: 评论页,展示某条热闻对应的长评和短评。

备注:项目地址为https://github.com/hongchh/simplified-zhihu-daily/tree/master/wxmp

一、项目结构和成果展示

入坑练习比较简单,暂时按照微信小程序开发者工具所创建的模板项目结构来。项目的文件结构和相关描述如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├─pages: 各个页面的代码
│ ├─detail: 详情页面
│ │ ├─detail.js: 详情页面逻辑层代码
│ │ ├─detail.wxml: 详情页面结构
│ │ └─detail.wxss: 详情页面样式表
│ ├─discussion: 评论页面
│ │ ├─discussion.js: 评论页面逻辑层代码
│ │ ├─discussion.wxml: 评论页面结构
│ │ └─discussion.wxss: 评论页面样式表
│ └─index: 首页
│ ├─index.js: 首页逻辑层代码
│ ├─index.wxml: 首页页面结构
│ └─index.wxss: 首页样式表
├─utils: 工具函数
│ └─util.js: 工具函数
├─app.js: 应用逻辑
├─app.json: 应用公共配置
└─app.wxss: 应用公共样式

为了方便大家对这个入坑练习demo有个初步的了解,这里先放出最终的成果截图展示。

成果页面展示

二、开发首页

首页分为两个部分,顶部有一个轮播,接着是一个热闻列表。轮播可以使用小程序提供的<swiper>组件来实现,比较简单。点击轮播项的时候需要跳转到相应的热闻详情页面,此处给最外层的<swiper>容器添加一个事件处理器,通过事件委托处理子元素的点击事件。子元素通过data-id来标记,当点击事件冒泡到外层父元素上时便可知道用户点击的是哪一条热闻。

1
2
3
4
5
6
7
8
9
<swiper indicator-dots autoplay bindtap="goToStoryDetail">
<swiper-item wx:for="{{ topStories }}" wx:key="{{ item.id }}" data-id="{{ item.id }}">
<view class="top-stories-item" style="background-image: url({{ item.image }})" data-id="{{ item.id }}">
<view class="top-stories-item-text" data-id="{{ item.id }}">
<text data-id="{{ item.id }}">{{ item.title }}</text>
</view>
</view>
</swiper-item>
</swiper>

热闻列表是一个分组的列表,显示热闻的时候按天显示。每组热闻的前面都会显示所属的时间,例如下图中的“今日热闻”、“7月5日 星期四”。因此这里的列表渲染是一个两重循环。逻辑层先将数据格式化好,视图层利用两层wx:for即可完成列表的渲染。最后,点击任一条热闻都需要跳转到相应的详情页,这里的处理方式与轮播那里一样,使用事件委托在最外层父元素上挂一个事件处理器即可。

热闻列表

1
2
3
4
5
6
7
8
9
<view id="stories-list" bindtap="goToStoryDetail">
<block wx:for="{{ storiesByDay }}" wx:key="{{ index }}" wx:for-item="stories">
<text id="stories-time">{{ stories.date }}</text>
<view class="stories-list-item" wx:for="{{ stories.stories }}" wx:key="{{ item.id }}" data-id="{{ item.id }}">
<text data-id="{{ item.id }}">{{ item.title }}</text>
<image src="{{ item.images[0] }}" data-id="{{ item.id }}"></image>
</view>
</block>
</view>

逻辑层中使用wx.request来获取数据,进入页面会触发onShow钩子,在onShow钩子中调用加载最近热闻的方法把首屏数据加载即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Page({
data: {
topStories: [],
storiesByDay: [],
beforeDay: ''
},

/**
* 进入页面加载今日热闻
*/
onShow () {
this.getStories()
},

/**
* 获取首页数据
*/
getStories () {
wx.request({
url: 'https://news-at.zhihu.com/api/4/news/latest',
success: (res) => {
this.setData({
topStories: res.data.top_stories,
storiesByDay: [{
stories: res.data.stories,
date: '今日热闻'
}],
beforeDay: res.data.date
})
}
})
}
// ...
})

首屏数据只包含了今天的热闻,过往的热闻需要在页面上拉触底的时候进行加载。小程序页面上拉触底的时候会触发onReachBottom钩子,在onReachBottom钩子中调用加载过往热闻的接口即可,加载完成后通过setData把数据变化反映到视图层。过往热闻的时间按照“XX月XX日 星期X”的格式进行展示,在处理响应数据的时候通过工具函数对日期进行格式化即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const utils = require('../../utils/util.js')

Page({
data: {
topStories: [],
storiesByDay: [],
beforeDay: ''
},

// ...

/**
* 上拉触底,加载更多热闻
*/
onReachBottom () {
this.getMoreStories()
},

/**
* 加载过往热闻
*/
getMoreStories () {
wx.request({
url: 'https://news-at.zhihu.com/api/4/news/before/' + this.data.beforeDay,
success: (res) => {
this.data.storiesByDay.push({
stories: res.data.stories,
date: utils.formatDate(res.data.date)
})
this.setData({
storiesByDay: this.data.storiesByDay,
beforeDay: res.data.date
})
}
})
}
})

最后,点击首页热闻跳转至详情页,可以通过wx.navigateTo来实现。跳转的时候通过url将热闻的id传递给详情页的逻辑层。

1
2
3
4
5
6
7
8
9
10
11
12
Page({
// ...

/**
* 点击某条热闻,跳转到热闻详情
*/
goToStoryDetail (e) {
wx.navigateTo({
url: '/pages/detail/detail?id=' + e.target.dataset.id
})
}
})

页面样式参考了知乎日报APP中的样式,部分数值以及交互方式可能不一致,由于没有设计稿,样式都是根据感觉大致调一下。微信小程序的样式表跟传统的CSS几乎一样,除了部分需要说明的地方之外,本文对样式部分将不进行过多阐述。

三、开发详情页

详情页的内容是HTML富文本,调用详情接口会返回一段HTML和一个CSS样式表链接。由于微信小程序不能直接渲染HTML,因此这里还需要做一次富文本解析,将HTML转化为WXML。(下图中的body字段即为热闻的详细内容)

详情接口返回的数据

这里我选择了wxParse来进行富文本解析,用法参照github上的描述,比较简单。请求接口获取数据后将HTML解析成WXML,渲染到页面上显示出来。

1
2
3
<view id="story-content">
<template is="wxParse" data="{{ wxParseData:story.nodes }}"/>
</view>

1
2
3
4
5
6
7
wx.request({
url: 'https://news-at.zhihu.com/api/4/news/' + this.data.storyId,
success: (res) => {
WxParse.wxParse('story', 'html', res.data.body, this, 5)
// ...
}
})

接口返回的CSS样式表链接中包含有HTML标签名,在小程序这边不适用,因此接口返回的样式表可以直接舍弃。自己根据需要调整样式。wxParse本身有自己的样式表,显示出来也还可以。我这里对几个地方补充点样式作调整。通过开发者工具的Wxml面板查看解析后的标签类名,在wxss中对相应的类添加样式。

Wxml面板

例如作者信息,下面是调整样式前后的对比图。

富文本样式调整

在这个demo中,详情页还实现了分享和跳转功能。由于小程序顶部的状态栏空间不太够,这里没有像知乎日报APP一样把操作按钮放在顶栏,而是固定在屏幕右下角。另外,由于没有引入图标库,微信小程序提供的icon又太少,这里暂时用字符图标代替。实现页面分享只需要设置按钮的open-type字段为share,然后在逻辑层的onShareAppMessage钩子中设置相关参数即可。

1
2
3
4
<view id="action">
<button plain id="share" open-type="share"></button>
<button plain id="discuss" bindtap="goToDiscussion"></button>
</view>

1
2
3
4
5
6
7
8
9
10
11
Page({
// ...
onShareAppMessage () {
return {
title: this.data.title,
imageUrl: this.data.image,
path: '/pages/detail/detail?id=' + this.data.storyId
}
}
// ...
})

跳转至评论页的实现与之前的从首页跳转到详情页的操作相同,使用wx.navigateTo实现页面跳转,通过url的查询字符串传递参数。

1
2
3
4
5
6
7
8
Page({
// ...
goToDiscussion () {
wx.navigateTo({
url: '/pages/discussion/discussion?id=' + this.data.storyId
})
}
})

四、开发评论页

评论页比较简单,没有多余的操作,只需要在页面展示的时候调用接口获取长评以及短评数据即可。长评和短评的展示格式一样,下面是长评列表的WXML结构,使用wx:for进行列表渲染。跟详情页一样,由于没有合适的icon,这里点赞的icon使用字符代替。

1
2
3
4
5
6
7
8
9
10
<text id="long-comments-bar">{{ longComments.length }}条长评</text>
<view class="comments" wx:for="{{ longComments }}" wx:key="{{ index }}">
<view class="info">
<image class="avatar" src="{{ item.avatar }}" />
<text class="author">{{ item.author }}</text>
<text class="likes">❤ {{ item.likes }}</text>
</view>
<text class="content">{{ item.content }}</text>
<text class="time">{{ item.time }}</text>
</view>

数据使用wx.request调用接口获取,长评和短评的接口数据格式都一样,因此写到同一个函数里。获取数据后都需要对数据中每条评论的时间进行格式化,转化为我们想要展示的格式,最后再交给视图层渲染展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Page({
// ...

/**
* 加载长评或短评
* @param { Number } type 评论类型,0-长评,1-短评
*/
getComments (type) {
const postfix = ['/long-comments', '/short-comments']
wx.request({
url: 'https://news-at.zhihu.com/api/4/story/' + this.data.storyId + postfix[type],
success: (res) => {
// 格式化评论发表时间
for (let i = 0; i < res.data.comments.length; ++i) {
res.data.comments[i].time = utils.formatTime(res.data.comments[i].time)
}
// 设置数据
if (type === 0) {
this.setData({ longComments: res.data.comments })
} else {
this.setData({ shortComments: res.data.comments })
}
}
})
}
})

五、总结

至此,简化版知乎日报的3个页面已经开发完成。由于demo比较简单,写起来比较快没什么难度,算是一次简易的入坑练习。虽然简单,但写的过程中也有发现点“不便之处”。无法在js中直接动态修改样式,尤其是内联样式。切换样式只能通过现在wxss中写好,然后通过数据绑定切换类名或id来实现。在详情页中,使用wxParse解析富文本后,它会给作者头像这个image元素添加一个内联样式,设置了图像高度,这个高度应该是wxParse根据某种规则计算出来的。在从详情页跳转到评论页再返回的时候,这里的计算会出现问题,导致高度变得很大,如下图所示。

内联样式问题

这里如果在wxss中相关的类名里面指明图像的width/height,会由于优先级低于内联样式而失效,无法解决这个问题。如果能在js中修改内联样式,便可以消除这个影响,可惜查了文档后发现并没有这种操作。最后是采取了下面这种做法,设置最大宽高度max-width/max-height,避开了与内联样式的冲突,又限制了图像的宽高,消除了这个影响。

1
2
3
4
.answer .meta .avatar {
max-width: 34px;
max-height: 34px;
}

刚入坑,了解不多,后续再继续踩坑继续了解~

分享到