原生框架开发入门(四) —— 博客文章详情页实现(上)
创建新页面
在微信开发者工具中打开小程序项目,在 pages
目录下新增 detail
目录用于存放文章详情页面,选中 detail
目录,右键弹出快捷菜单,点击「新建 Page」,输入 detail
创建详情页相关文件。
此时,项目根目录下的 app.json
配置文件中,pages
配置项会自动新增 pages/detail/detail
页面。
路由导航
在微信小程序的 WXML 视图文件中,不能直接像 HTML 那样通过 a
标签定义链接进行跳转,而是需要通过在元素上绑定点击事件进行跳转处理。
对于小程序内部页面,可以在点击事件处理函数中通过 wx.navigateTo 进行跳转,通过这种方式跳转不会关闭当前页,我们可以在新页面通过 wx.navigateBack 跳转回去;此外我们还可以通过 wx.redirectTo 跳转到应用内指定页面,但是这种跳转方式会关闭当前页。
对于小程序外部页面,比如标准的 URL 链接,可以通过 web-view 组件来装载,但是目前个人类型小程序不支持该组件。因此,对个人类型的小程序而言,是无法实现外部链接的访问的。
我们这个博客项目比较简单,只支持首页和详情页两个页面,目前不涉及外部链接,所以我们只演示内部页面导航。我们在 pages/index/index.wxml
页面渲染文章列表时为每个文章卡片容器绑定组件事件处理函数 postDetail
,并且设置属性 data-id
,以便可以在事件处理函数中获取文章 ID:
<view wx:for="{{articles}}">
<view data-id="{{item.id}}" bindtap="postDetail" wx:if="{{item.id}}">
<card title="{{item.title}}" content="{{item.summary}}" date="{{item.posted_at}}" views="{{item.views}}" thumbnail="{{item.thumb}}"/>
</view>
</view>
bindtap
属性的含义是当该元素被点击时,触发执行绑定的 postDetail
函数。下面我们在 index.js
中定义这个事件处理函数:
postDetail: function(event) {
wx.navigateTo({
url: '/pages/detail/detail?id=' + event.currentTarget.dataset.id,
})
},
我们在 wx.naviagteTo
API 中传入指定页面路径,并且带上页面参数,以便可以在详情页获取对应文章数据进行渲染。
重新编译小程序,在首页文章列表点击某个文章,就可以进入文章详情页了:
现在详情页还是默认提供的内容:
<!--pages/detail/detail.wxml-->
<text>pages/detail/detail.wxml</text>
接下来,我们来完善详情页的内容显示。
文章详情页渲染
detail.wxml
我们规划在详情页页显示文章标题、作者信息、发布时间、正文、浏览数、点赞数等信息,所以我们编写 pages/detail/detail.wxml
内容如下:
<view class="container">
<view class="title">{{article.title}}</view>
<view class="meta-header">
<text class="author">{{article.author}}</text>
<text class="posted-date">{{article.posted_at}}</text>
</view>
<view class="content">
<rich-text nodes="{{article.content}}"></rich-text>
</view>
<view class="meta-footer">
<text class="views">{{article.views}}</text>
<text class="votes">{{article.votes}}</text>
</view>
<text class="info" wx:if="{{info}}">{{info}}</text>
</view>
其它都比较好理解,对于文章正文,我们使用富文本组件 rich-text 来渲染。
detail.js
接下来,我们打开 detail.js
,定义渲染数据对象如下:
/**
* 页面的初始数据
*/
data: {
article: {
id: 1,
title: '测试文章标题',
author: '学院君',
post_date: '2019-01-10',
content: [
{
name: 'p',
attrs: {
class: 'p_class'
},
children: [{
type: 'node',
name: 'img',
attrs: {
class: 'img_class',
src: 'https://statics.laravelacademy.org/wp-content/uploads/2015/07/Laravel学院Logo.png'
}
}]
},
{
name: 'h3',
attrs: {
class: 'h3_class',
},
children: [{
type: 'text',
text: '愿景'
}]
},
{
name: 'p',
attrs: {
class: 'p_class'
},
children: [{
type: 'text',
text: 'Laravel框架是一个为Web工匠准备的PHP框架,让你从意大利面条一样杂乱的代码中解放出来,从而快速构建简洁、优雅、功能强大的web应用。'
}]
},
{
name: 'p',
attrs: {
class: 'p_class'
},
children: [{
type: 'text',
text: 'Laravel学院致力于提供优质的Laravel中文学习资源,帮助你快速上手,从入门到精通,让这个世界上最流行的PHP框架在中国拥有更多拥趸。'
}]
},
{
name: 'p',
attrs: {
class: 'p_class'
},
children: [{
type: 'text',
text: '这是一个纯粹的关于学习与分享、知识与技能的平台,学院君抛个砖,希望大家相互帮助,共同进步,让学习与进取者不再孤单。'
}]
}
],
views: 1000,
votes: 100
},
info: ''
},
我们为文章对象填充了测试数据,并且将 article.content
属性值以数组方式进行重构以便对正文样式进行自定义,同时提高富文本渲染性能。
detail.wxss
最后,我们对文章详情页样式进行优化,编辑 detail.wxss
文件内容如下:
/* pages/detail/detail.wxss */
@font-face {
font-family: "iconfont";
src: url("iconfont.eot?t=1545831519864");
/* IE9*/
src: url("iconfont.eot?t=1545831519864#iefix") format("embedded-opentype"), url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff"), url("iconfont.ttf?t=1545831519864") format("truetype"), url("iconfont.svg?t=1545831519864#iconfont") format("svg");
/* iOS 4.1- */
font-weight: normal;
font-style: normal;
}
.container {
-webkit-font-smoothing: antialiased;
padding: 0 25rpx;
height: auto;
}
.title {
font-size: 18px;
font-weight: 500;
}
.meta-header {
font-size: 14px;
color: #666;
opacity: .8;
text-align: left;
width: 100%;
margin: 20rpx 0 25rpx;
font-family: 'iconfont';
}
.meta-header .author:before {
content: '\e688';
margin-right: 10rpx;
}
.meta-header .posted-date {
margin-left: 30rpx;
}
.meta-header .posted-date:before {
content: '\e64e';
margin-right: 10rpx;
}
.meta-footer {
font-size: 14px;
color: #666;
opacity: .8;
text-align: left;
width: 100%;
margin: 15rpx 0 20rpx;
font-family: 'iconfont';
}
.meta-footer .views:before {
content: '\e666';
margin-right: 10rpx;
}
.meta-footer .votes {
margin-right: 10rpx;
float: right;
}
.meta-footer .votes:before {
content: '\e61a';
margin-right: 10rpx;
}
.content {
-webkit-font-smoothing: antialiased;
width: 100%;
font-size: 14px;
color: #424242;
margin: 20rpx 0;
justify-content: space-between;
flex-wrap: wrap;
}
.content:after {
content: "";
flex: auto;
}
.content.description {
margin-bottom: 40rpx;
color: #999;
}
.content .p_class {
margin: 0 0 15rpx;
outline: 0;
padding: 0;
vertical-align: baseline;
}
.content .img_class {
max-width: 100%;
height: auto;
clear: both;
display: block;
margin: 0 auto;
}
.content .h3_class {
font-size: 16px;
margin-top: 20px;
margin-bottom: 10px;
clear: both;
}
我们这里同样使用了 @font-face
通过 CSS 来渲染小图标,具体实现思路参考首页那里的介绍,我们在这里新增了作者、点赞数图标。此外,我们还在这里自定义了 rich-text
组件中正文的样式。以后有新的元素,都可以在这里进行定义。
编写完 detail.wxss
,重新编译小程序,就可以在微信开发者工具左侧的预览区看到渲染后的文章详情页了:
这里的文章数据都是写死的测试数据,在下一篇教程中,我们将编写后端文章接口 API,然后在小程序详情页中通过异步请求获取指定文章数据,动态渲染文章详情页,并且引入第三方的 wxParse 组件来实现富文本渲染。
1 Comment
页面实际使用的是
posted_at
测试数据是post_date