微信小程序之小豆瓣圖書

本文發表至今已有一段時間,錯別字多、文筆混亂、內容過於陳舊。本人建議讀者沒必要細究,大概瀏覽便可,最新的開發指南仍是以官方文檔爲準,該博文的示例代碼通過了重構,已經與官方文檔同步,可能與文中的代碼片斷有較大差別,請以 Github 倉庫上的代碼爲準。css

最近微信小程序被炒得很火熱,本人也抱着試一試的態度下載了微信web開發者工具,開發工具比較簡潔,功能相對比較少,個性化設置也沒有。瞭解完開發工具以後,順便看了一下小程序的官方開發文檔,大概瞭解了小程序的開發流程和一些經常使用的API。html

瞭解了小程序以後,本身就有了想要作一個小demo的衝動,雖然本身對小程序尚未作過不少實踐,只是在官方例子上徘徊,可是仍是想作出點小東西。既然要作一個demo,天然須要到數據,本身有又不想獨自搭建服務端,因此在網上搜索能夠用來提供測試數據的免費api,最後我選擇了豆瓣圖書。豆瓣圖書提供的api功能比較少,加上不開放appkey申請,因此沒法操做用戶數據。只能作點簡單的圖書查詢和圖書詳細信息展現,這個demo只有兩個頁面,很是之簡單。vue

豆瓣圖書API

demo中用到的豆瓣圖書api只有兩個,一個是圖書搜索,另外一個是獲取圖書詳情。react

搜索圖書

GET https://api.douban.com/v2/book/searchjquery

參數 意義 備註
q 查詢關鍵字 q和tag必傳其一
tag 查詢的tag q和tag必傳其一
start 取結果的offset 默認爲0
count 取結果的條數 默認爲20,最大爲100

返回status=200git

{
  "start": 0,
  "count": 10,
  "total": 30,
  "books" : [Book, ...]
}

獲取圖書詳情

GET https://api.douban.com/v2/book/:idgithub

參數 意義
:id 圖書id

如下是具體圖書的詳情信息,部分demo中用不到的信息省略web

{
    "id":"1003078",
    "title":"小王子",
    "alt":"https:\/\/book.douban.com\/subject\/1003078\/",
    "image":"https://img3.doubanio.com\/mpic\/s1001902.jpg",
    "author":[
        "(法)聖埃克蘇佩裏"
        ],
    "publisher":"中國友誼出版公司",
    "pubdate":"2000-9-1",
    "rating":{"max":10,"numRaters":9438,"average":"9.1","min":0},
    "author_intro":"聖埃克蘇佩裏(1900-1944)1900年,瑪雅·戴斯特萊姆......",
    "catalog":"序言:法蘭西玫瑰\n小王子\n聖埃克蘇佩裏年表\n"
}

Demo編寫

建立項目

項目取名爲DouBanBookApp,項目的結構小程序默認的結構同樣ajax

DouBanBookApp
    pages
        index 首頁
            index.js
            index.wxml
            index.wxss
        detail 詳情頁
            detail.js
            detail.wxml
            detail.wxss
    requests 
        api.js API地址
        request.js 網絡請求
    utils
        util.js 工具
    app.js
    app.json
    app.wxss

應用的主調色參考了豆瓣app的色調,採用了偏綠色。json

首頁

首頁頂部展現搜索輸入框,用戶輸入圖書名稱,點擊搜索按鈕,展現圖書列表。圖書可能會不少,不能一會兒所有展現,須要用到分頁,app上最多見的列表分頁就是上拉加載模式,根據小程序提供的組件中,找到了一個比較符合場景的scroll-view組件,這個組件有一個上拉到底部自動觸發的bindscrolltolower事件。

先製做出界面的靜態效果,以後再整合API,因爲本人對界面設計不敏感,因此隨便弄了一個粗糙的佈局,看得過去就好了,嘿嘿~~

index.wxml

<view class="search-container">
  <input type="text" placeholder="輸入書名搜索"></input><icon type="search" size="20"/>
</view>

<scroll-view scroll-y="true" style="width:100%;position:relative;top:40px;height:200px">

    <view style="text-align:center;padding-top:50rpx;">
      <icon type="cancel" color="red" size="40" />
      <view><text>沒有找到相關圖書</text></view>
    </view>

    <view style="text-align:center;padding-top:50rpx;">
      <icon type="search" size="60" />
      <view><text>豆瓣圖書</text></view>
    </view>
    
    <view class="header">
      <text>圖書 10本圖書</text>
    </view>

    <view class="common-list">

    <view class="list-item">
      <view class="index-list-item">
        <view class="cover">
          <image class="cover-img" src="images/demo.png"></image>
        </view>
        <view class="content">
          <view class="title">圖書標圖</view>
          <text class="desc">9.0/oopsguy/2016-07-08</text>
        </view>
      </view>
    </view>

    </view>

    <view class="refresh-footer">
      <icon type="waiting" size="30" color="reed"  />
    </view>

</scroll-view>

index.wxss

page {
  background: #F2F1EE;
}

/*seach*/
.search-container {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  background-color: #42BD56;
  color: #FFF;
  height: 40px;
  padding: 0 10rpx;
  z-index: 100;
}
.search-container input {
  background: #FFF;
  color: #AAA;
  margin-top: 5px;
  padding: 5px 10rpx;
  height: 20px;
  border-radius: 8rpx;
}
.search-container icon {
  position: absolute;
  top: 10px;
  right: 20rpx;
}

/*header*/
.header {
  padding: 20rpx 30rpx;
}
.header text {
  color: #A6A6A6;
}

/*common list*/
.list-item {
  position: relative;
  overflow: hidden
}

/*index list*/
.index-list-item {
  background: #FFF;
  padding: 15rpx 30rpx;
  overflow: hidden;
}
.index-list-item::active {
  background: #EEE;
}
.index-list-item .cover {
  float: left;
  width: 120rpx;
  height: 160rpx;
  overflow: hidden
}
.index-list-item .cover image.cover-img {
  width: 120rpx;
  height: 160rpx;
}
.index-list-item .content {
  margin-left: 140rpx;
}
.index-list-item .title {
  display: inline-block;
  height: 90rpx;
  padding-top: 20rpx;
  overflow: hidden;
}
.index-list-item .desc  {
  display: block;
  font-size: 30rpx;
  padding-top: 10rpx;
  color: #AAA;
  white-space:nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.refresh-footer {
  text-align: center;
  padding: 10rpx 0;
}

圖書詳細頁面

圖書詳細頁面就是展現具體的圖書信息,通用首頁穿過了的圖書id來獲取圖書信息以後在展現出來,獲取的過程當中可能有延遲,須要一個加載效果來過渡。

detail.wxml

<view>
    <view class="cover-container">
        <image src="images/demo.png"></image>
    </view>

    <view class="book-meta">
        <view class="meta-info">
            <text class="book-title">圖書標題</text>
            <text class="other-meta">做者:做者名稱</text>
            <text class="other-meta">出版社:xxx出版社</text>
            <text class="other-meta">出版日期:2010-05-07</text>
        </view>
        <view class="range">
            <text class="score">0</text>
            <text class="viewers">0</text>
        </view>
    </view>

    <view class="book-intro">
        <view class="intro-header"><text>簡介</text></view>
        <text class="intro-content">
            這是圖書簡介
        </text>
    </view>

    <view class="book-intro">
        <view class="intro-header"><text>做者</text></view>
        <text class="intro-content">
            這是做者簡介
        </text>
    </view>
</view>

<loading>
    加載中...
</loading>

detail.wxss

page {
    background: #EEE;
}
.cover-container {
    background: #42BD56;
    text-align: center;
    padding: 50rpx 0;
}
.cover-container image {
    display: inline-block;
    width: 300rpx;
    height: 400rpx;
}

.book-meta {
    position: relative;
    padding: 20rpx;
    overflow: hidden;
}
.book-meta .range {
    position: absolute;
    top: 30rpx;
    right: 20rpx;
    width: 180rpx;
    background: #FFF;
    padding: 20rpx 10rpx;
    text-align: center;
    box-shadow: 2px 2px 10px #CCC;
}
.book-meta .meta-info {
    margin-right: 200rpx;
}
.meta-info text {
    display: block
}
.book-title {
    font-weight: bold;
    font-size: 50rpx;
}
.other-meta {
    padding-top: 10rpx;
    color: #888;
    font-size: 30rpx;
}
.range text {
    display: block;
}
.range .score {
    font-size: 50rpx;
    font-weight: bold;
}
.range .starts {
    font-size: 40rpx;
}
.range .viewers {
    font-size: 30rpx;
}

.book-intro {
    padding: 20rpx;
    font-size: 40rpx;
}
.book-intro .intro-header {
    color: #888
}
.book-intro .intro-content {
    font-size: 35rpx;
    line-height: 45rpx;
}

作好了首頁和詳細頁的靜態頁面,接下來就是經過網絡請求api來獲取數據,並顯示到頁面上來。

網絡請求和數據處理

爲了更好的管理api,我把api專門放到了一個單獨的api.js文件中

api.js

const API_BASE = "https://api.douban.com/v2/book";

module.exports = {
  API_BOOK_SEARCH: API_BASE + "/search",
  API_BOOK_DETAIL: API_BASE + "/:id"
}

有些常常用到的工具函數放到了util.js中

util.js

function isFunction( obj ) {
  return typeof obj === 'function';
}

module.exports = {
  isFunction: isFunction
}

微信小程序提供了一個用於網絡請求的api:wx.request(OBJECT),具體的參數跟jquery的ajax方法差很少,爲了方便調用,我把網絡請求放到了request.js中

request.js

var api = require('./api.js');
var utils = require('../utils/util.js');

/**
 * 網路請求
 */
function request(url, data, successCb, errorCb, completeCb) {
    wx.request({
        url: url,
        method: 'GET',
        data: data,
        success: function(res) {
            utils.isFunction(successCb) && successCb(res.data);
        },
        error: function() {
            utils.isFunction(errorCb) && errorCb();
        },
        complete: function() {
            utils.isFunction(completeCb) && completeCb();
        }
    });
}

/**
 * 搜索圖書
 */
function requestSearchBook(data, successCb, errorCb, completeCb) {
    request(api.API_BOOK_SEARCH, data, successCb, errorCb, completeCb);
}

/**
 * 獲取圖書詳細信息
 */
function requestBookDokDetail(id, data, successCb, errorCb, completeCb) {
    request(api.API_BOOK_DETAIL.replace(':id', id), data, successCb, errorCb, completeCb);
}

module.exports = {
  requestSearchBook: requestSearchBook,
  requestBookDokDetail: requestBookDokDetail
}

首頁有圖書搜索和列表展現,上拉加載的效果。微信小程序中沒有了DOM操做的概念,一切的界面元素的改變都要經過數據變化來改變,因此須要在js中的Page中的data中聲明不少數據成員。

用戶在輸入數據時,輸入框的input綁定了searchInputEvent事件,就回捕獲到輸入的數據,把輸入的數據更新的data中的searchKey中。

searchInputEvent: function( e ) {
    this.setData( { searchKey: e.detail.value });
}

當點擊搜索按鈕是,觸發tap事件,其綁定了searchClickEvent

searchClickEvent: function( e ) {
    if( !this.data.searchKey )
      return;
    this.setData( { pageIndex: 0, pageData: [] });
    requestData.call( this );
}

requestData中封裝了請求圖書列表的方法

/**
 * 請求圖書信息
 */
function requestData() {
  var _this = this;
  var q = this.data.searchKey;
  var start = this.data.pageIndex;

  this.setData( { loadingMore: true, isInit: false });
  updateRefreshBall.call( this );

  requests.requestSearchBook( { q: q, start: start }, ( data ) => {
    if( data.total == 0 ) {
      //沒有記錄
      _this.setData( { totalRecord: 0 });
    } else {
      _this.setData( {
        pageData: _this.data.pageData.concat( data.books ),
        pageIndex: start + 1,
        totalRecord: data.total
      });
    }
  }, () => {
    _this.setData( { totalRecord: 0 });
  }, () => {
    _this.setData( { loadingMore: false });
  });
}

上拉加載的效果是一個小球不停的變換顏色,須要一個顏色列表

//刷新動態球顏色
var iconColor = [
  '#353535', '#888888'
];

而後用一個定時器來動態改變小球圖標的顏色

/**
 * 刷新上拉加載效果變色球
 */
function updateRefreshBall() {
  var cIndex = 0;
  var _this = this;
  var timer = setInterval( function() {
    if( !_this.data[ 'loadingMore' ] ) {
      clearInterval( timer );
    }
    if( cIndex >= iconColor.length )
      cIndex = 0;
    _this.setData( { footerIconColor: iconColor[ cIndex++ ] });
  }, 100 );
}

詳細頁面的顯示須要到首頁點擊了具體圖書的id,因此須要首頁傳值過來,這裏用到了小程序土工的wx.navigateTo方法,給其指定的url參數後面帶以查詢字符串格式形式的參數,被跳轉的頁面就會在onLoad方法中獲得值。

//跳轉到詳細頁面
toDetailPage: function( e ) {
    var bid = e.currentTarget.dataset.bid; //圖書id [data-bid]
    wx.navigateTo( {
      url: '../detail/detail?id=' + bid
    });
}

detail.js中接受參數

onLoad: function( option ) {
    this.setData({
      id: option.id
    });
}

其實小程序的頁面製做跟平時的html和css差很少,只是頁面中不能用傳統的html標籤,而是改用了小程序提供的自定義標籤,小程序對css的支持也有限制,注意哪些寫法不兼容也差很少懂了。操做頁面變化是經過數據變化來表現出來的,這點有點像react和vue。以上的demo用到的知識點並很少,主要是頁面的數據綁定、事件綁定、模版知識和網絡請求等相關api。仔細看看文檔也差很少能夠作出一個小例子。
qi

最終效果圖

整體來講,Demo很簡單,只有兩個頁面,界面也是醜醜的T_T,算是我入門小程序的第一課吧。

DouBanBookApp首頁

DouBanBookApp列表

DouBanBookApp詳情

示例代碼

https://github.com/oopsguy/wechat-miniprogram-examples

http://git.oschina.net/oopsguy/WechatSmallApps

相關文章
相關標籤/搜索