原生框架开发入门(四) —— 博客文章详情页实现(上)


创建新页面

在微信开发者工具中打开小程序项目,在 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 中传入指定页面路径,并且带上页面参数,以便可以在详情页获取对应文章数据进行渲染。

重新编译小程序,在首页文章列表点击某个文章,就可以进入文章详情页了:

-w338

现在详情页还是默认提供的内容:

<!--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 组件来实现富文本渲染。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 原生框架开发入门(三) —— 博客首页文章列表实现(下)

>> 下一篇: 原生框架开发入门(五) —— 博客文章详情页实现(下)