启动项目

在学习了微信小程序之后,我使用wxml页面可以做出唐诗的内容了。结合wxss,我可以定义好看的布局。在小程序需要备案时,因为唐诗需要资质,而我个人无法满足这些条件,所以我需要开发新的项目。恰好最近在为我的技术笔记发愁。我希望有一个可以展示我笔记内容的工具。而visit 就是一个用于展示和搜索文章的小程序工具。

项目需求

我的最初想法非常简单,1,可以更新文章内容而不需要升级小程序。2,需要在小程序端进行展示。3,能够根据标题或者关键字进行搜索文章内容。

技术选型

  • 小程序使用原生开发
  • 界面 UI 使用 WeUI
  • 内容渲染使用Markdown

知识储备

需要你了解微信小程序开发基础知识,Markdown内容编写,以及towxml库。

完成首页页面

首页页面非常简单,一个标题描述,用来解释小程序干什么?一个搜索框用来搜索内容,三个快捷按钮用于快速查找内容。

写首页的时候,需要用到的技术要点:

  • 搜索框、按钮、列表、文本
  • 界面交互
  • 网络请求、数据存储

在微信开发者工具,app.json 文件中配置首页,配置后会自动生成首页模板页面。

"pages": [
    "pages/index/index"
  ],

在 pages/index/index.wxml 文件中,编写首页内容。包含标题,描述,搜索框,以及快捷按钮。

<view class="page">
  <view class="page__hd">
    <view class="page__title">使用说明</view>
    <view class="page__desc">收集了很多经典的代码片段和库,是学习编程的好工具。可通过关键字查找内容,用于解决开发中遇到的问题。</view>
  </view>
  <view class="page__bd page__bd_spacing">
    <!-- 搜索框-->
    <view class="weui-search-bar {{inputShowed ? 'weui-search-bar_focusing' : ''}}" id="searchBar">
      <form class="weui-search-bar__form">
        <view class="weui-search-bar__box">
          <i class="weui-icon-search"></i>
          <input type="text" confirm-type="search" class="weui-search-bar__input" placeholder="请输入您要查找的内容" value="{{inputVal}}" focus="{{inputShowed}}" bindinput="inputTyping" bindconfirm="search" />
        </view>
        <label class="weui-search-bar__label" bindtap="showInput">
          <i class="weui-icon-search"></i>
          <span class="weui-search-bar__text">搜索</span>
        </label>
      </form>
      <view class="weui-search-bar__cancel-btn" bindtap="hideInput">取消</view>
    </view>

    <!-- 查询快捷按钮-->
    <view class="weui-flex">
      <view class="weui-flex__item">
        <view class="placeholder" id="btnHot" bindtap="bindBtn" hover-class="placeholder-hover">最火</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="btnNew" bindtap="bindBtn" hover-class="placeholder-hover">最新</view>
      </view>
      <view class="weui-flex__item">
        <view class="placeholder" id="btnCold" bindtap="bindBtn" hover-class="placeholder-hover">最冷</view>
      </view>
    </view>
    <!-- 内容-->
  </view>
</view>

当使用模拟器预览时,发现首页内容是正确的,但是样式非常丑, 我使用 WeUI 来美化一下,使用weui是因为虽然它不是最美,但它的兼容性和稳定性最好,如果自己写wxss,需要考虑各种机型和设备。

从 Github 搜索 weui-wxss,下载后,将 weui.xss 中的内容复制到项目中的 app.wxss 中。复制后,这个 weui 样式就在整个项目中生效。如果要某个页面单独调整样式,可以在页面中 wxss 中单独修改样式。

首页数据交互

在画完页面之后,需要进行互动,小程序中和数据进行交互需要写JS文件。在JS文件中设置数据和方法,可以和页面进行互动。

根据首页页面实际情况,搜索框,快捷按钮都需要互动。先说搜索框,搜索框需要记录用户输入的内容,当按钮点击搜索,或者回车时,还需要根据用户输入的内容,去后台请求数据。

在 index.js 文件中,我们添加数据字段:

data: {
    artList: [],
    inputShowed: false,
    inputVal: "",
    keyword: "",
  },

它们分别表示输入值,关键字,文章内容。还需要添加方法:


  showInput: function () {
    this.setData({
      inputShowed: true
    });
  },
  hideInput: function () {
    this.setData({
      inputVal: "",
      inputShowed: false,
      keyword: "",
      artList: [],
    });

  },


  inputTyping: function (e) {
    this.setData({
      inputVal: e.detail.value
    });
  },

  search(e) {
    var keyword = this.data.inputVal.toLowerCase()
    this.setData({
      keyword: keyword,
      op: 1,
    })

    this.searchArt(1, keyword)
  },

其中,inputTyping 和 search 方法是记录搜索内容,并向后台请求数据,其它两个方法是为了美化搜索框的效果。

当后台返回数据后,会赋值给 artList,它是一个数组列表,我们循环读取渲染它的内容。在 index.wxml 中的代码如下:

 <!-- 内容-->
    <!--循环输出-->
    <view class="weui-cells">
      <block wx:for="{{artList}}" wx:key="uuid" >
        <view aria-labelledby="js_cell_l1_bd " aria-describedby="js_cell_l1_note" class="weui-cell weui-cell_access" hover-class="weui-cell_active" bindtap="jump" data-idx="{{index}}" data-guid="{{item.uuid}}" data-stars="{{item.views}}">
          <view class="weui-cell__bd" id="js_cell_l1_bd" aria-hidden="true">
            <view>{{item.title}}</view>
          </view>
          <view class="weui-cell__ft weui-cell__ft_in-access" aria-hidden="true">
            <text wx:if="{{ item.lockState == 1 }}" id="js_cell_l1_note" aria-label=",锁" class="weui-badge weui-badge_dot">锁</text>
            <text wx:elif="{{ item.createTime > yestTime }}" id="js_cell_l1_note" aria-label=",新" class="weui-badge weui-badge_dot">新</text>
            <!-- <text wx:elif="{{ item.updateTime > item.createdTime }}" id="js_cell_l1_note" aria-label=",有更新" class="weui-badge weui-badge_dot">修</text> -->
            <text wx:elif="{{ item.views > 50 }}" id="js_cell_l1_note" aria-label=",火" class="weui-badge weui-badge_dot">热</text>
            <text wx:elif="{{ item.views > 500 }}" id="js_cell_l1_note" aria-label=",非常火" class="weui-badge weui-badge_dot">爆</text>
            <text wx:elif="{{ item.views > 5000 }}" id="js_cell_l1_note" aria-label=",超级火" class="weui-badge weui-badge_dot">燃</text>
          </view>
        </view>
      </block>
    </view>
    <!--循环输出-->

为了更好的体验,可在列表中根据浏览次数,显示不同的修饰词。让用户更好的了解到那篇文章质量更高,更受欢迎。点击列表后,会跳转到文章详情。通过 jump 方法实现。当在视图上需要更多的参数时,可以使用 data-格式,这样会传给 js 具体值,在请求后台数据章节会用到。

首页中还有 3 个快捷按钮,分别为最新、最火、最冷。根据文章的浏览量来排序展示。按钮本身有点击属性,当点击按钮时,将根据按钮设置的参数去后台查询对应的数据。这三个按钮都使用同一个查询方法,为了区分三个按钮,可以使用 view 的 id 属性。代码如下:

  bindBtn: function (e) {
    let btnId = e.target.id;
    let qtype = 1;
    switch (btnId) {
      case "btnHot":
        // 按浏览次数倒序排序,再按创建升序
        qtype = 1;
        break;
      case "btnNew":
        // 按创建倒序排序
        qtype = 2;
        break;
      case "btnCold":
        // 按浏览次数升序排序,再按创建升序
        qtype = 3;
        break;
      default:
        break;
    }
    this.setData({
      artList: [],
      qtype: qtype,
      op: 2,
    })

    this.getArtList(1, qtype)
  },

使用网络请求后台数据

如果只有静态页面,会缺少一些灵气。现在网络无处不在,掌握网络请求,是一项必须的技能。

小程序 HTTP 网络开发非常简单。小程序官方提供了 HTTP 的三个请求 API,分别是网络请求 request,上传文件 uploadfile,下载文件 downloadfile。

可使用这三个 API,去实现我们的需求。在使用本地开发者工具开发小程序时,默认不要勾选检验域名,方便我们调试。当上线的时候,我们需要将后台的域名配置到小程序后台域名白名单中。

核心是请求文章列表。我设计了两个接口,一个接口根据关键字查询文章列表,一个接口根据快接按钮属性查询文章列表。

1,根据按钮属性查询文章列表代码如下:


  // 根据类型查询
  getArtList: function (pageNo, qtype) {
    const that = this

    that.loading = true
    wx.showLoading({
      title: '加载中...',
      mask: true,
    })
    if (pageNo === 1) {
      that.setData({
        artList: [],
      })
    }

    httpGet('/artl', {
      'pageNo': pageNo,
      'qtype': qtype,
    }).then((res) => {
      that.loading = false
      wx.hideLoading()

      const result = res.data;
      if (result.code == 1) {
        const articles = result.data;
        that.setData({
          page: pageNo, //当前的页号
          pages: result.count, //总页数
          artList: that.data.artList.concat(articles)
        })

      } else {
        wx.showToast({
          title: '没有找到记录',
        })
      }
    }).catch((err) => {
      that.loading = false
      wx.hideLoading()
      wx.showToast({
        title: '网络异常请重试',
      })
    })

  },

2,根据关键字查询文章列表代码如下:


  searchArt: function (pageNo, keyword) {
    const that = this

    that.loading = true
    wx.showLoading({
      title: '加载中...',
      mask: true,
    })
    if (pageNo === 1) {
      that.setData({
        artList: [],
      })
    }

    httpGet('/sart', {
      'pageNo': pageNo,
      'keyword': keyword,
    }).then((res) => {
      that.loading = false
      wx.hideLoading()
      const result = res.data;
      if (result.code == 1) {
        const articles = result.data;
        that.setData({
          page: pageNo, //当前的页号
          pages: result.count, //总页数
          artList: that.data.artList.concat(articles)
        })

      } else {
        wx.showToast({
          title: '没有找到记录',
        })
      }
    }).catch((err) => {
      that.loading = false
      wx.hideLoading()
      wx.showToast({
        title: '网络异常请重试',
      })
    })

  },

两个方法大体框架差不多,后台接口名不一样。在这里,我多说两句,这两个接口可以合并成一个接口,有好处也有坏处。好处是减少接口的数量,坏处是减少了灵活性,耦合性太强,修改一个接口内容会影响到另一个接口。所以大家可以根据实际需求来设计。我经历的好多接口都是合并之后,在新增需求后又再次拆开,然后继续优化时会再次合并。

网络请求这块知识点不多,大多时间都是和后台的调试。这次没有用到文件上传和文件下载。这两个 API 也非常的简单。我的另一个小程序【豆子工具】中有用到,可以看看它的项目。

当获取到数据后,赋值给 artList,小程序会自动渲染数据到页面。另外两个数据 page 和 pages 是为了分页使用。

 that.setData({
          page: pageNo, //当前的页号
          pages: result.count, //总页数
          artList: that.data.artList.concat(articles)
        })

当数据渲染到首页后,我们就可以看到文章列表,当点击文章列表中的某个文章项时,会跳转到文章详情。这次我们使用小程序解析 Markdown 格式内容。

解析 Markdown 文件

当我们使用网络请求后台数据后,赋值给 artList,这样首页页面就展示了文章列表。当我们点击文章列表中的某项时,会再次请求后台,获取文章的详情。文章的详情是 Markdown 数据。

在小程序中,无法直接展示 Markdown 数据,我们需要将 Markdown 转换为 WXML,网上的大神很多,有多款 Markdown 转 WXML 库,我使用的是 towxml 库。我使用Markdown主要是因为更新内容不用升级小程序,这是动态渲染的最大优点。当然它还有其它一些优势。

1,按照 towxml 库的说明文档,我们将编译后的 towxml 复制到我们的项目中,然后在 app.js 文件中引入这个组件。

App({
  towxml: require('./towxml/index'),

2,按照首页的步骤,我们添加文章详情页面。

"pages/article/index"

在 index.wxml 文件中,我们添加页面布局,这个很简单,显示渲染后的内容即可。

<!--pages/article/index.wxml-->
<view class="page">

  <!--使用towxml-->
  <towxml nodes="{{article}}" />

</view>

3,注意,我们还需要在 index.json 中添加组件引入。

{
  "usingComponents": {
    "towxml": "../../towxml/towxml"
  }
}

4,在 js 文件中,我们需要调用 Markdown 数据。

onLoad(options) {

    const _ts = this;
    wx.showLoading({
      title: '加载中',
    })

    httpGet('/artd', {
      uuid: options.guid,
    }).then((res) => {
      const result = res.data;
      if (result.code == 1) {
        let content = result.data;
        let obj = app.towxml(content, 'markdown', {
          theme: 'light',
          events: {
            tap: (e) => {
              console.log('tap', e);
            }
          }
        });

        _ts.setData({
          article: obj,
        });

        wx.hideLoading({
          success: (res) => { },
        })
      } else {
        wx.hideLoading()
      }
    }).catch((err) => {
      console.log(err);
      wx.hideLoading()
      wx.showToast({
        title: '网络异常请重试',
      })
    })

  },

我们在 onLoad 中加载函数,这样页面启动就会自动网络请求,然后渲染 Markdown 数据。在请求数据时,我们注意到 guid,这个参数是上一个页面即首页,点击文章列表项带过来的数据。它就是上节中,在 view 中设置 data-属性,可以将页面参数传给 JS。

首页跳转到详情页方法如下:

jump: function (e) {
    const guid = e.currentTarget.dataset.guid
    // 调整到文章页面
    wx.navigateTo({
      url: '../article/index?guid=' + guid,
    })
  },

现在,关于文章展示和搜索的功能就完成了。