组件化框架 WePY 开发入门(三) —— 博客文章详情页重构


上篇教程学院君介绍了如何通过小程序框架 WePY 重构博客小程序首页,这篇我们接着来重构博客小程序文章详情页,后端 API 接口还是保持不变,主要工作在微信小程序前端页面,首先我们需要在 blog-lite 项目中创建详情页页面。

创建新页面

在 PhpStorm 中打开小程序项目,在 src/pages 目录下创建 post.wpy 用于渲染博客详情页:

然后初始化 post.wpy 页面代码如下:

<style lang="scss">

</style>

<template>
    <view class="container">

    </view>
</template>

<script>
    import wepy from 'wepy'

    export default class Post extends wepy.page {
        config = {
            navigationBarTitleText: '文章详情页'
        };

        data = {
            article: {},
            info: '',
            post_url: ''
        };
    }
</script>

最后在 src/app.wpy 中添加详情页页面:

export default class extends wepy.app {
  config = {
    pages: [
      'pages/index',
      'pages/post'
    ],
    
    ...  

路由导航

接下来,我们需要在博客小程序首页文章列表实现点击卡片跳转到文章详情页。和原生框架类似,我们也是通过处理点击事件来实现,并且仍然基于 wx.navigateTo 进行跳转。

在小程序首页页面 src/pages/index.wpy 模板代码中,我们已经定义了点击事件监听处理函数:

<view class="cards-area">
  <repeat for="{{articles}}">
    <view @tap="tap('{{item.id}}')" wx:if="{{item.id}}">
      <card :title="item.title" :content="item.summary" :category="item.category" :date="item.posted_at" :views="item.views" :thumbnail="item.thumb"/>
    </view>
  </repeat>
</view>

在 WePY 框架中,将原生框架的 bindtap 属性调换成了 @tap,含义是一样的,当点击该卡片时,就会调用 tap 函数并传入文章 ID 进行跳转操作,具体实现代码如下:

methods = {
    tap(id) {
        wx.navigateTo({
            url: `/pages/post?id=${id}`  // 打开一个新的同路由页面,指定对应的文章ID
        })
    }
}

我们在 wx.naviagteTo API 中传入指定页面路径,并且带上页面参数,以便可以在详情页获取对应文章数据进行渲染。

重新编译小程序,在首页文章列表点击某个文章,就可以进入文章详情页了,只不过此时是空白页:

接下来,我们来完善详情页的内容渲染。

文章详情页渲染

引入wxParse

我们还是通过 wxParse 来渲染文章正文富文本内容,因此需要在 blog-lite 项目中引入这个第三方组件。

src 目录下创建一个 resources 子目录用于存放第三方资源,然后 miniapp 项目中的 wxParse 拷贝到 resources 目录下即可。

内容模板

文章详情页显示内容和之前一样,所以我们编写 post.wpytemplate 模板内容如下:

<template>
    <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">
            <import src="../resources/wxParse/wxParse.wxml"/>
            <template is="wxParse" data="{{wxParseData:article_content.nodes}}"/>
        </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>
</template>

正文部分我们引入 wxParse 进行处理。

页面逻辑

接下来,我们在 post.wpy<script></script> 中定义页面渲染逻辑:

<script>
    import wepy from 'wepy'
    import wxParse from '../resources/wxParse/wxParse'

    export default class Post extends wepy.page {
        config = {
            navigationBarTitleText: '文章详情页'
        };

        data = {
            article: {},
            info: '',
            post_url: ''
        };

        onLoad(options = {}) {
            var that = this
            wx.showLoading({
                title: '加载中'
            })
            wx.request({
                url: `https://blog.laravelacademy.org/api/v1/article/${options.id}`,
                success: (res) => {
                    that.article = res.data.data
                    wxParse.wxParse('article_content', 'html', that.article.content, that, 1)
                    this.$apply()
                    wx.hideLoading()
                }
            })
        }
    }
</script>

页面逻辑比较简单,就是在页面加载时通过异步请求从后端 API 接口获取文章详情数据,然后将其同步到页面变量 article 上,再通过动态绑定的机制更新到模板视图中。

这里需要注意的是在 WePY 框架中,可以直接通过 options 获取 URL 中的指定参数值,比原生框架方便。另外,WePY 框架通过脏数据检查实现对 setData 的封装,在异步方法里设置完属性后,需要调用 this.$apply() 触发脏数据检查,这样才能保证更新生效(详情请参考官方手册)。

样式代码

最后,我们对文章详情页样式进行优化,在 post.wpy 文件的 style 标签中编写样式代码如下:

<style lang="scss">
    @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';
        .author:before {
            content: '\e688';
            margin-right: 10rpx;
        }
        .posted-date {
            margin-left: 30rpx;
            &: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';
        .views:before {
            content: '\e666';
            margin-right: 10rpx;
        }
        .votes {
            margin-right: 10rpx;
            float: right;
            &:before {
                content: '\e61a';
                margin-right: 10rpx;
            }
        }
    }

    .content {
        &:after {
            content: "";
            flex: auto;
        }
        &.description{
            margin-bottom: 40rpx;
            color: #999;
        }
        -webkit-font-smoothing: antialiased;
        width: 100%;
        font-size: 14px;
        color: #424242;
        margin: 20rpx 0;
        justify-content: space-between;
        flex-wrap: wrap;
    }

    @import "../resources/wxParse/wxParse.wxss";

    .content .wxParse-p {
        margin: 0 0 15rpx;
        outline: 0;
        padding: 0;
        vertical-align: baseline;
    }
</style>

至此,我们就已经完成了文章详情页的主体代码编写。重新编译小程序,就可以在微信开发者工具左侧的预览区点击博客首页文章卡片进入文章详情页,并看到渲染后的文章信息了:

分享文章

我们还可以在当前页面中通过编写 onShareAppMessage 方法实现分享文章详情页的功能:

onShareAppMessage() {
  let id = this.article.id
  let title = this.article.title
  return {
      title: `Laravel学院 - ${title}`,
      path: `/pages/post?id=${id}`
  }
}

分享方式和前面几篇教程介绍的一样,效果图也和前面演示的一样:

好了,到这里,我们已经通过微信小程序组件化框架 WePY 完成了对博客小程序首页和详情页的重构,接下来,我们还要继续探索一些更高级功能的开发,比如用户登录、点赞功能的实现,使我们的小程序具备简单的用户交互功能,完成了这些功能,就可以将这个博客小程序上线了。


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

<< 上一篇: 组件化框架 WePY 开发入门(二) —— 博客首页文章列表重构

>> 下一篇: 博客小程序项目代码上传、发布上线及访问统计