微信小程序之API篇——豆瓣图书搜索(十二)

微信小程序之入门篇(一)
微信小程序之注册篇(二)
微信小程序之开发初体验(三)——开发工具使用和目录结构
微信小程序之生命周期(四)
微信小程序之数据绑定(五)
微信小程序之触控事件(六)
微信小程序之基础组件篇——视图容器(七)
微信小程序之基础组件篇——基础内容(八)
微信小程序之基础组件篇——表单组件(九)
微信小程序之基础组件篇——导航组件(十)
微信小程序之基础组件篇——媒体组件(十一)
微信小程序之API篇——豆瓣图书搜索(十二)
微信小程序之拓展篇——weui-wxss(十三)

他山之石可以攻玉。

本文作为小程序的最后一篇文章,介绍学习大神的代码实现的豆瓣图书搜索功能。采用了网络请求功能,这也是大部分小程序需要的一个功能模块。
注:豆瓣图书代码忘记博客出处了,求大神原谅。

涉及知识
1、布局、数据绑定、模板
2、调用API——wx.request,豆瓣图书接口
3、跳转豆瓣图书详情页

实现效果如下:

粗鲁的我,直接进入主题吧。
首先介绍目录结构,如下图。

目录结构

接下来贴上所有代码和图片。
app.js

//app.js
App({
  onLaunch: function () {
    //调用API从本地缓存中获取数据
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)
  },
  getUserInfo:function(cb){
    var that = this
    if(this.globalData.userInfo){
      typeof cb == "function" && cb(this.globalData.userInfo)
    }else{
      //调用登录接口
      wx.login({
        success: function () {
          wx.getUserInfo({
            success: function (res) {
              that.globalData.userInfo = res.userInfo
              typeof cb == "function" && cb(that.globalData.userInfo)
            }
          })
        }
      })
    }
  },
  globalData:{
    userInfo:null
  }
})

app.json

{
  "pages": [
    "pages/index/index",
    "pages/detail/detail"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#42BD56",
    "navigationBarTitleText": "豆瓣图书",
    "navigationBarTextStyle": "white"
  }
}

app.wxss

page {
  font-family: 'microsoft yahei';
  height: 100%;
}

/*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 {
  font-size: 35rpx;
  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;
}

util.js

function formatTime( date ) {
  var year = date.getFullYear()
  var month = date.getMonth() + 1
  var day = date.getDate()

  var hour = date.getHours()
  var minute = date.getMinutes()
  var second = date.getSeconds()
  
  return [ year, month, day ].map( formatNumber ).join( '/' ) + ' ' + [ hour, minute, second ].map( formatNumber ).join( ':' )
}

function formatNumber( n ) {
  n = n.toString()
  return n[ 1 ] ? n : '0' + n
}

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

function parseInteger(val) {
  if (isNaN(val))
    return 0;
  return parseInt(val);
}

module.exports = {
  formatTime: formatTime,
  isFunction: isFunction,
  parseInteger: parseInt
}

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"
}

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) {
            if (res.statusCode == 200)
                utils.isFunction(successCb) && successCb(res.data);
            else
                console.log('请求异常', res);
        },
        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);
}

/**
 * 关键字是否是tag
 */
function requestHasTag(tag, successCb, errorCb, completeCb) {
    request(api.API_BOOK_SEARCH, {tag: tag, count: 1}, successCb, errorCb, completeCb);
}

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

index.js

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

//刷新动态球颜色
var iconColor = [
  '#42BD56', '#31A040'
];

Page({
  data: {
    scrollHeight: 0, //scroll-view高度
    pageIndex: 0, //页码
    totalRecord: 0, //图书总数
    isInit: true, //是否第一次进入应用
    loadingMore: false, //是否正在加载更多
    footerIconColor: iconColor[0], //下拉刷新球初始颜色
    pageData: [], //图书数据
    searchKey: null //搜索关键字
  },

  //页面显示获取设备屏幕高度,以适配scroll-view组件高度
  onShow: function () {
    wx.getSystemInfo({
      success: (res) => {
        console.log(res)
        this.setData({
          scrollHeight: res.windowHeight - (100 * res.windowWidth / 750) //80为顶部搜索框区域高度 rpx转px 屏幕宽度/750
        });
      }
    })
  },

  //搜索输入框输入取值
  searchInputEvent: function (e) {
    this.setData({ searchKey: e.detail.value });
  },

  //搜索按钮点击事件
  searchClickEvent: function (e) {
    if (!this.data.searchKey) {
      return;
    }
    this.setData({ pageIndex: 0, pageData: [] });
    requestData.call(this);
  },

  //下拉请求数据
  scrollLowerEvent: function (e) {
    if (this.data.loadingMore)
      return;
    requestData.call(this);
  },

  //跳转到详细页面
  toDetailPage: function (e) {
    var bid = e.currentTarget.dataset.bid; //图书id [data-bid]
    wx.navigateTo({
      url: '../detail/detail?id=' + bid
    });
  }

});

/**
 * 请求图书信息
 */
function requestData() {
  var _this = this;
  var q = this.data.searchKey;
  var start = this.data.pageIndex;

  this.setData({ loadingMore: true, isInit: false });
  updateRefreshBall.call(this);
  console.log(start)
  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 });
  });
}

/**
 * 刷新下拉效果变色球
 */
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);
}

index.wxml

<view class="search-container">
  <input type="text" bindinput="searchInputEvent" placeholder="输入书名搜索"></input><icon bindtap="searchClickEvent"  type="search" size="20"/>
</view>

<scroll-view scroll-y="true" style="height:{{scrollHeight}}px"
  bindscrolltolower="scrollLowerEvent">

    <view class="logo" wx:if="{{!loadingMore && totalRecord == 0 && !isInit}}">
      <icon type="cancel" color="#B0AAAA" size="50" />
      <view><text>没有找到相关图书</text></view>
    </view>

    <view class="logo" wx:if="{{isInit}}">
      <image src="../../images/book.png" />
      <view><text>豆瓣图书</text></view>
      <text style="font-size:30rpx;">Designed by Oopsguy</text>
    </view>

    <view class="header" wx:if="{{totalRecord > 0 && !isInit}}">
      <text>图书 {{totalRecord}}本图书</text>
    </view>

    <view class="common-list" wx:if="{{totalRecord > 0}}">

      <block wx:for="{{pageData}}">
        <view class="list-item" data-bid="{{item.id}}" bindtap="toDetailPage">
          <view class="index-list-item">
            <view class="cover">
              <image class="cover-img" src="{{item.image}}"></image>
            </view>
            <view class="content">
              <view class="title">{{item.title}}</view>
              <text class="desc">{{item.rating.average == '0.0' ? '无' : item.rating.average}}/<block wx:for="{{item.author}}" wx:for-item="it" wx:key="*this">{{it}}/</block>{{item.pubdate}}</text>
            </view>
          </view>
        </view>
      </block>

    </view>

    <view class="refresh-footer" wx:if="{{loadingMore}}">
      <icon type="waiting" size="30" color="{{footerIconColor}}"  />
    </view>

</scroll-view>

index.wxss

page {
  background: #F2F1EE;
}

.logo {
  text-align:center;
  padding-top:50rpx;
  font-size: 40rpx;
  font-weight: bold;
  color: #AAA
}
.logo image {
  width: 250rpx;
  height: 250rpx;
}

.search-container {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #42BD56;
  color: #FFF;
  height: 80rpx;
  padding: 10rpx 20rpx;
  z-index: 100;
}
.search-container input {
  background: #FFF;
  color: #AAA;
  padding: 5px 10rpx;
  height: 40rpx;
  width: 100%;
  border-radius: 8rpx;
}
.search-container icon {
  position: absolute;
  z-index: 10;
  top: 50%;
  margin-top: -20rpx;
  right: 40rpx;
}

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

detail.js

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

Page( {
  data: {
    id: null,
    loadidngHidden: false,
    bookData: null
  },
  onLoad: function( option ) {
    console.log(option)
    this.setData({
      id: option.id
    });
  },
  onReady: function() {
    var id = this.data.id;
    var _this = this;
    requests.requestBookDokDetail(
      id, 
      {fields: 'image,summary,publisher,title,rating,pubdate,author,author_intro,catalog'}, 
      ( data ) => {
        console.log(data)
        _this.setData({
          bookData: data
        });
    }, () => {
      wx.navigateBack();
    }, () => {
      _this.setData( {
        loadidngHidden: true
      });
    });
  }
});

detail.wxml

<view wx:if="{{bookData}}">
    <view class="cover-container">
        <image src="{{bookData.image}}"></image>
    </view>

    <view class="book-meta">
        <view class="meta-info">
            <text class="book-title">{{bookData.title}}</text>
            <text class="other-meta">作者:<block wx:for="{{bookData.author}}" wx:for-item="it" wx:key="*this">{{it}} </block></text>
            <text class="other-meta">出版社:{{bookData.publisher}}</text>
            <text class="other-meta">出版日期:{{bookData.pubdate}}</text>
        </view>
        <view class="range">
            <text class="score">{{bookData.rating.average}}</text>
            <text class="viewers">{{bookData.rating.numRaters ? bookData.rating.numRaters : 0}}参与</text>
        </view>
    </view>

    <view class="book-intro" wx:if="{{bookData.summary}}">
        <view class="intro-header"><text>简介</text></view>
        <text class="intro-content">{{bookData.summary}}</text>
    </view>

    <view class="book-intro" wx:if="{{bookData.author_intro}}">
        <view class="intro-header"><text>作者</text></view>
        <text class="intro-content">{{bookData.author_intro}}</text>
    </view>
</view>

<loading hidden="{{loadidngHidden}}">
    加载中...
</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: 40rpx;
}
.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 {
    margin-bottom: 40rpx;
    padding: 20rpx;
    background: #FAFAFA;
}
.book-intro .intro-header {
    color: #888;
    font-weight: bold;
    padding: 40rpx 0;
}
.book-intro .intro-content {
    font-size: 35rpx;
    line-height: 2;
    text-align: justify
}

book.png


book.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容