iBistu 4.0终于在几位同学的努力下正式推出,我也没想到iBistu怎么就发展到了4.0,四年多过去了,还能“隐约”看到学长们的代码,这种“恍如隔世”的感觉每写下一行代码就会冒出。

在学习开发微信小程序的过程中,一直在苦于追求到底哪些项目才适合与新手小白去磨练,某天下午突然想到iBistu新闻模块的这种“不干不湿”情况正适合于练手各种前端框架(我认为小程序也是一种前端框架)

iBistu新闻模块在API文档上写的非常清楚,我们只需要传入一些参数即可获取到校内新闻的JSON数据,而对于微信小程序来说,解析JSON格式的数据是最为方便不过了。再加上iBistu 4.0简洁的UI风格,不会让我这种第一次上手前端开发的同学感到阻力很大,反而还会有一种“过五关,斩六将”的feel~😝

经过上次的小程序初探,了解了小程序的整体架构、开发要求和规范,再加上参与了iBistu 4.0的iOS端开发,预估开发成本不大且获取开发经验可观,遂,开干~


从iBistu的API文档中可以看到,想要获取新闻数据,我们需要传入category(新闻分类)、page(分页数)、api_key和session_token(login后获取)四个参数给api.iflab.org/api/v2/newsapi/newslist这个接口

其实这第一步就有点恶心了,最开始我想的是,把iBistu新闻模块单独抽出来,作为一个独立的小程序供大家使用,但是获取iBistu所有数据的前提是,你得登录!!!想了一会儿,有两种解决方案,要么在小程序上单独做一个login入口,要么就自己再搭一个后台,第一次获取用户iBistu用户名和密码后把这些数据都存下来,以后每次做网络请求发现token过期时在后台自动刷新token。

其实从开发成本上来说,我会毫不犹豫的选择第一种,这样会很快,但是给用户的体验非常不好,在iBistu端上要登录,用你这个小程序也要登录,单从开发者角度上来想就很扯,做完自己都不想用。

现目前暂定第二种方案,但是为了加速开发时间,这部分工作挪到寒假再做吧。🙂来看看最终的效果:



从完成图效果上来看,除了不能侧滑切换新闻主题外,剩余部分已经达到甚至部分细节超过了Native,而且这还是在微信开发工具里的模拟器上显示出来的效果,因为没有注册AppID,要不然就能在微信端上预览效果了。

那,为什么不注册AppID呢?因为iBistu的数据源挂在在api.iflab.org下,iflab.org并没有在国内备案,如果你在微信小程序后台注册了AppID,你就得添加可信域名,这个可信域名不但要求是添加了SSL证书还得是通过了备案的,遂,iflab.org因此GG。

不过这样也好,新闻数据因为都是开发团队里的一个学长去学校官网上爬的,学校官网本身就没有加SSL证书,通过image标签去加载拿到的imageLink还得再去小程序后台上添加一次对应的域名,加上还没SSL证书,因此也GG。_(:зゝ∠)_


这是iBistu新闻小程序的整体文件结构,就比微信小程序提供的开发模板多了四个文件而已,整个小程序总体大小为49KB(大家千万记住,小程序对开发文件大小限制在了1M以内)。

index是新闻列表页,content为列表item内容页,我们首先来拆分列表页,红色为cell中的单个标签,蓝色为一个cell

iBistu在iOS端上采取了新建两个xib来完成两种不同样式的加载只有title和什么都有的cell,但在小程序中,我们只需要一个样式就足够了,因为新闻的整体listView和每条新闻cell都采用了flex弹性布局,如果布局中的元素hidden了能够自动“弹”回去,因此,我们的index.wxml可以这么来写cell,

1
2
3
4
5
6
7
8
9
<view class='contentView'>
<view>
<view class='cell'>
<text class='cellTitle'>标题</text>
<image class='cellImage' src='#图片' />
<text class='cellContent'>内容</text>
</view>
</view>
</view>

关于顶部的tab导航栏,实际上是一个水平滚动的scroll-view(小程序也提供了跟UIScrollView一样的scroll-view喔~),关于它的元素布局我们可以在index.wxml中这么写,

1
2
3
4
5
6
<scroll-view class='titleHeadView' scroll-x='true'>
<view class='singleHeadView'>
<text class='titleHeadText'>tab的内容</text>
<view class='titleHeadBottomLineView'></view>
</view>
</scroll-view>

这样,我们就拿到了一个搭好初步框架的新闻主体,接下来,我们去美化它。在index.wxss中可以这么写,

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
.contentView {
margin-top: 30px;
}

.cell {
display: flex;
flex-direction: column;
margin-top: 10px;
border-top: 10px solid #efeff3;
}

.cellTitle {
padding: 10px;
font-weight: bold;
font-size: 17px;
}

.cellImage {
width: 100%;
height: 200px;
}

.cellContent {
/* padding对换行布局有冲突只能用这种傻傻的方式去写了 */
margin-left: 10px;
margin-top: 10px;
margin-right: 10px;
display: -webkit-box;
font-size: 28rpx;
line-height: 40rpx;
word-break: break-all;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}

.titleHeadView {
white-space: nowrap;
position: fixed;
background-color: #FFF;
top: 0;
}

.singleHeadView {
display: flex;
flex-direction: column;
display: inline-block;
margin-left: 5px;
margin-right: 5px;
}

.titleHeadText {
font-size: 14px;
}

.titleHeadBottomLineView {
height: 2px;
width: 100%;
background-color: #000;
}

当然,wxss的样式定义是像素级copyiBistu的,如果大家觉得不好看的话,自行修改吧。现在,我们就已经把View层的东西都整理好了,接下来要做的事情就是去做网络请求拉到数据,再填充到View层中来即可。


首先来看顶部的scroll-view的数据填充,从API文档中可以看到,要求我们传入分类,而不是从一个接口取得所有新闻分类的信息,也就是说,新闻的分类信息我们要本地写死,

1
2
3
4
5
6
7
8
9
10
data: {
// 新闻列表数据
resData: [],
titleData_en: ['zhxw', 'tpxw', 'rcpy', 'jxky', 'whhd', 'xyrw', 'jlhz', 'shfw', 'mtgz'],
titleData_cn: ['综合新闻', '图片新闻', '人才培养', '教学科研', '文化活动', '校园人物', '交流合作', '社会服务', '媒体关注'],
// 标记ScrollView每个item的底部lineView是否显示
titleIsHiddens: [false, true, true, true, true, true, true, true, true, true, true],
// 记录当前点击的ScrollView.item的下标
titleIndex: 0,
},

正式开始进行网络请求工作之前,我们还差一个也是最重要的参数未知,session_token,这个参数是用户登录后返回的字段,24小时之内未带上这个token进行请求,则失效。emmm,我的做法就是先去运行一个iBistu的Xcode工程,拿到token后再粘回来,先这么简单粗暴的做着。

对api.iflab.org/api/v2/newsapi/newslist这个接口请求数据,需要附带四个参数,拼接完的wx.request如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在onLoad中写下这个函数
wx.request({
url: 'https://api.iflab.org/api/v2/newsapi/newslist',
method: 'GET',
data: {
// 综合新闻
category: 'zhxw',
// 首页
page: 0,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
// 打印出请求回来的数据
console.log(res)
}, fail: function (res) {
}, complete: function () {
}
})

因为有几个地方需要用到api_key和token,因此我们可以把它丢入到app.js中,然后再通过使用跟上文一样的getApp().globalData即可取到全局数据(可以说跟pch文件和NSUserDefault非常类似了。)

1
2
3
4
5
globalData: {
userInfo: null,
session_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIzYTE4OWM0NDZhOWNlMzQ0M2NjMDQ1YmQyZTM4ZDA4YyIsImlzcyI6Imh0dHBzOi8vYXBpLmlmbGFiLm9yZy9hcGkvdjIvdXNlci9zZXNzaW9uIiwiaWF0IjoxNTE1NzcwNzczLCJleHAiOjE1MTU4NTcxNzMsIm5iZiI6MTUxNTc3MDc3MywianRpIjoiZUFLZ3FkVXZUQk8xOXdldiIsInVzZXJfaWQiOjQyLCJmb3JldmVyIjpmYWxzZX0.EuW_8rxXPv-EuB1oKe9OQMMuGrEQTFuDC5QGebqP3J4',
api_key: '3528bd808dde403b83b456e986ce1632d513f7a06c19f5a582058be87be0d8c2'
}

此时,我们运行小程序,即可看到在控制台看到请求回来的信息。现在要把信息都展示到view上,我们需要在wxml中用到wx:for和wx:if这两个东西,补充完后的wx:request如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
wx.request({
url: 'https://api.iflab.org/api/v2/newsapi/newslist',
method: 'GET',
data: {
category: 'zhxw',
page: 0,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
wx.hideLoading()

that.setData({
resData: res.data
})
console.log(res)
}, fail: function (res) {
}, complete: function () {
}
})

补充完的index.wxml如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<scroll-view class='titleHeadView' scroll-x='true'>
<view class='singleHeadView' wx:for='{{ titleData_cn }}' catchtap='titleHeadViewTapClick' id='{{ index }}'>
<text class='titleHeadText'>{{ item }}</text>
<view class='titleHeadBottomLineView' hidden='{{ index==titleIndex?false:true }}'></view>
</view>
</scroll-view>

<view class='contentView'>
<!-- catchtap含义后文讲 -->
<view wx:for='{{ resData }}' catchtap='contentViewTapClick' id='{{ index }}' >
<view class='cell'>
<text class='cellTitle'>{{ item.newsTitle }}</text>
<image class='cellImage' src='{{ item.newsImage }}' mode='aspectFill' wx:if='{{ item.newsImage.length > 0 }}' />
<text class='cellContent' wx:if='{{ item.newsImage.length > 0 }}'>{{ item.newsIntro }}</text>
</view>
</view>
</view>

需要注意的是,当我们使用wx:for去动态加载wxml中的标签时,在填充数据的时候要可以使用小程序提供的遍历对象item,这个item你可以认为是C++里的迭代器,通过迭代器去访问遍历到的每一个对象中的值。当然,如果你不想用它提供的item,你可以使用当前的循环遍历index,index代表了当前循环到的下标,然后通过下标去指定输出内容。

此时再次运行小程序,即可看到新闻数据和顶部的tab数据加载出来啦~~!!!我们再进一步,点击scroll-view上的tab标签切换新闻内容!想要做到这一点,首先要给tab添加点击事件,小程序提供了冒泡事件和非冒泡事件,简单来说就是一个是触摸事件可以逐层向上传递,另外一个不能,具体使用方法大家可以去看小程序开发文档,那里的讲解更加详细。在此,根据需求我给view绑定的是非冒泡事件catchtap,实现如下

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
titleHeadViewTapClick: function (event) {
var that = this

wx.showLoading({
title: '加载中',
})

that.data.titleIndex = event.currentTarget.id
this.setData({
titleIndex: this.data.titleIndex
})
wx.request({
url: 'https://api.iflab.org/api/v2/newsapi/newslist',
method: 'GET',
data: {
// 根据获取点击的id来选择传入的分类字段数据
category: this.data.titleData_en[event.currentTarget.id],
page: 0,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
wx.hideLoading()

that.setData({
resData: res.data
})
}, fail: function (res) {
}, complete: function () {
}
})
}

再运行工程,可以通过点击顶部tab来切换新闻啦~!!我们再往前推进,把上拉加载也做了,因为毕竟是新闻嘛,信息流的展示还是趋于给用户“无限”的感觉。在index.json中添加enablePullDownRefresh字段,开启上拉功能,

1
2
3
{
"enablePullDownRefresh": true
}

因为是上拉加载,那么必定会涉及到数据的分页,也就是前文中我们所说的page字段的作用,page字段每+1,数据就会返回时间上相对之前返回的时间更早一些的数据。我们要做的效果是追加数据,注意!是追加!更新完后的整个index.js如下所示

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
var p = 0
// 拉取分页数据方法
var GetList = function (that) {
wx.request({
url: 'https://api.iflab.org/api/v2/newsapi/newslist',
method: 'GET',
data: {
category: 'zhxw',
page: p,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
wx.hideLoading()
var l = that.data.resData
for (var i = 0; i < res.data.length; i++) {
l.push(res.data[i])
}
that.setData({
resData: l
});
p++;
}, fail: function (res) {
}, complete: function () {
}
})
}

Page({
data: {
resData: [],
titleData_en: ['zhxw', 'tpxw', 'rcpy', 'jxky', 'whhd', 'xyrw', 'jlhz', 'shfw', 'mtgz'],
titleData_cn: ['综合新闻', '图片新闻', '人才培养', '教学科研', '文化活动', '校园人物', '交流合作', '社会服务', '媒体关注'],
titleIsHiddens: [false, true, true, true, true, true, true, true, true, true, true],
titleIndex: 0,
},

// 页面加载
onLoad: function () {
var that = this
GetList(that)
wx.showLoading({
title: '加载中',
})
},

// 顶部tab点击事件
titleHeadViewTapClick: function (event) {
var that = this

wx.showLoading({
title: '加载中',
})

that.data.titleIndex = event.currentTarget.id
this.setData({
titleIndex: this.data.titleIndex
})
wx.request({
url: 'https://api.iflab.org/api/v2/newsapi/newslist',
method: 'GET',
data: {
category: this.data.titleData_en[event.currentTarget.id],
page: 0,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
wx.hideLoading()

that.setData({
resData: res.data
})
console.log(res)
}, fail: function (res) {
}, complete: function () {
}
})
},

// 点击新闻跳转新闻详情
contentViewTapClick: function(event) {
var link = this.data.resData[event.currentTarget.id].newsLink
wx.navigateTo({
url: '../content/content?link=' + link,
})
},

onReachBottom: function () {
//上拉
var that = this
GetList(that)
}
})

大家从上文也看到了多了一个contentViewTapClick方法,这个方法就是我们后边要展开说的内容,从API文档上我们找到新闻详情接口,需要我们传入link字段数据,这个数据是个URL,问了学长,实际上就是给这个接口丢一个让它自己去实时抓数据的地址。hhhhh,这个做法确实巧妙。因此,我们需要在点击每条新闻的wx.navigationTo跳转方法时传入link字段的数据。


在content.js中写下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
onLoad: function (options) {
var that = this
wx.showLoading({
title: '加载中',
})
wx.request({
url: 'http://api.iflab.org/api/v2/newsapi/newsdetail',
method: 'GET',
data: {
link: options.link,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
console.log(res)
wx.hideLoading()
}, fail: function (res) {
}, complete: function () {
}
})
}

运行小程序,随便点击一条新闻,就会在控制台中打印出来了相关信息。刚开始我想偷个懒,想直接navigationTo拿到的link,没想到小程序居然不支持H5外链,只能跳转自身页面,所以只能自己拼数据了。

iBistu iOS端的新闻不是我写的,对于其中的一些实现新闻详情的巧妙方法我是一点都不了解,所以看到返回来的新闻详情数据后,整个人都不好了。

我的乖乖,您看懂是什么意思了么?一堆回车符的地方就是要插入图片的地方。😱。所以我们要根据回车符出现的地方来判断是否应该插入图片,看了看iBistu的新闻详情部分的实现,确实是这么做的。

但是更伤的问题来了,根据回车符我截断字符串在数组里,然后po出了内容,一看更呆了,图片在一个地方集中出现得越多,那么这块地方的换行符也就越多,换句话说,得根据回车符的多少来决定插入的图片数量。

嗯,其实这还不是最伤的,按照这个思路弄完了以后,浏览了前面几条新闻,完美对上了,但是!!!到了后边的几条新闻就全乱了。该出现图片的地方没出现,不该出现的图片的地方空一大片

原因是因为刚开始找到的规则是,三个换行放一张图片,如果当前区域放超过一张图片,比如说是两张图片,换行数则由三个变成了七个,也就是说,以三位基数,每多加两个换行多一张图片。

但事实上不是这样啊!😭。后边几条新闻的换行数跟图片数对应关系完全不符合之前找到的,全乱了。这就非常的难受了。弄到最后,实在没办法,决定了如果是多张图片就只放一张😔。

如果这部分你没能拿到真实数据好好研究一番的话,就算看了代码意义也不太大,

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
  Page({

/**
* 页面的初始数据
*/
data: {
resData_cn: [],
resData_image: [],
resData_display: [],
resDataCount: 0
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this
wx.showLoading({
title: '加载中',
})
wx.request({
url: 'http://api.iflab.org/api/v2/newsapi/newsdetail',
method: 'GET',
data: {
// 拿到NavigationTo拼接而来的参数
link: options.link,
api_key: getApp().globalData.api_key,
session_token: getApp().globalData.session_token
},
success: function (res) {
wx.hideLoading()
var resString = res.data.article.split('\n')
var resdata_cn = []
var resdata_cn_index = -1
var resdata_display = []
var tempIndex = 0
for (var i = 0; i < resString.length; i++) {
// 判断是否含有中文,若不含有中文则为图片
// 即换行符
if (/.*[\u4e00-\u9fa5]+.*$/.test(resString[i])) {
tempIndex = 0
resdata_cn_index++
resdata_cn[resdata_cn_index] = resString[i]
resdata_display[resdata_cn_index] = true
} else {
tempIndex++
// 这么搞新闻图片加载不全
if (tempIndex == 3) {
resdata_display[resdata_cn_index] = false
}
}
}

var count = resdata_cn.length + res.data.imgList.length

var displayArr = []
var displayIndex = 0
for (var j = 0; j < resdata_display.length; j++) {
if (resdata_display[j]) {
displayArr[j] = ""
} else {
displayArr[j] = res.data.imgList[displayIndex]
displayIndex ++
}
}

that.setData({
resData_image: displayArr,
resData_cn: resdata_cn,
resDataCount: count,
resData_display: resdata_display,
})
wx.hideLoading()
}, fail: function (res) {
}, complete: function () {
}
})
}
})

其中设置了很多BOOL变量,为啥要设置这么多的BOOL变量呢?我们来看一张图,

我对content.wxml做了如上的分割,实际上也是一个listView,每一个cell(蓝色)中都有text和image,其写法如下所示,

1
2
3
4
5
6
7
8
9
10
<view>

<view wx:for='{{ resData_cn }}'>
<view class='contentView'>
<text class='contentViewText' >{{ item }}</text>
<image class='contentViewImage' mode='widthFix' src='{{ resData_image[index] }}' hidden='{{ resData_display[index] }}' />
</view>
</view>

</view>

通过控制image标签的hidden来达到效果,这就是为什么设置了这么多BOOL变量的原因。😝


以上就是我二探小程序的历程,可能说的有些不够清晰,你可以在GitHub下载源码,自行研究一番,就能够理解我以上所讲述的东西了。

这次二刷小程序,给我的感觉是以前端上觉得做起来的简单的东西,在小程序上会越发的更加简单,在端上觉得比较困难的东西,小程序说不定会有一些比较奇妙的解法,甚至会有出其不意的地方。这两次学习小程序的开发,总的来说,让我很意外,小程序不但是给用户“用完即走”的感觉,给开发者也一身轻松,不过,也只是“小程序”。