【笔记】实战mpvue2.0多端小程序框架——图书详情


图书详情 | 「小慕读书」官网


一、学习重点

  • van-rate 组件的使用(查看官方文档
  • API对接:
    • 图书详情API
    • 评分API
    • 目录API
    • 加入书架API
    • 移出书架API
  • 微信小程序API:

二、图书详情视觉稿

http://www.youbaobao.xyz/mpvue-design/preview/#artboard9

三、搜索页面组件结构图

detail

四、搜索页面组件示意图

detail
新建src\pages\detail\detail.vue

<template>
  <div>
  </div>
</template>

<script>
export default {
  components: {},
  computed: {
  },
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>

<style lang="scss" scoped>
</style>

新建src\pages\detail\main.js

import Vue from 'vue'
import App from './detail'

const app = new Vue(App)
app.$mount()

新建src\pages\detail\main.json

{
  "navigationBarTitleText": "图书详情"
}

在src\pages\index\index.vue中为HomeCard绑定点击事件并完善事件内容:

<HomeCard
  :data="homeCard"
  @onClick="onHomeBookClick"
/>
onHomeBookClick(book) {
  this.$router.push({
    path: '/pages/detail/main',
    query: {
      fileName: book.fileName
    }
  })
}

为src\components\home\HomeCard.vue组件中的onBookClick事件新加传递参数(部分属性省略):

<div class="book-info">
  <div class="book-wrapper">
    <div class="book-img-wrapper" @click="onBookClick(item)">
onBookClick(item) {
  this.$emit('onClick', item)
},

同理,也为src\components\home\HomeBook.vue组件中的onBookClick事件新加传递参数(部分属性省略):

<div class="home-book-content">
  <div class="home-book-row">
    <div class="home-book-col">
      <div class="book-wrapper" @click="onBookClick(book)">
    onBookClick(book) {
      this.$emit('onBookClick', book)
    }

五、图书信息组件

图书详情中的图书信息组件

组件名称属性参数用途默认值
DetailBookpropsbook图书信息{}

新建src\components\detail\DetailBook.vue:

<template>
  <div class="detail-info">
    <div class="detail-info-l">
      <div class="book-img">
        <ImageView :src="book && book.cover"></ImageView>
      </div>
    </div>
    <div class="detail-info-r">
      <div class="book-title">{{(book && book.title) || ''}}</div>
      <div class="book-author">{{(book && book.author) || ''}}</div>
      <div class="book-category">{{(book && book.categoryText) || ''}}</div>
    </div>
  </div>
</template>

<script>
  import ImageView from '../base/ImageView'
  export default {
    components: { ImageView },
    props: {
      book: Object
    }
  }
</script>

<style lang="scss" scoped>
  .detail-info {
    display: flex;
    padding: 10px 15px;

    .detail-info-l {
      padding-right: 15px;

      .book-img {
        width: 100px;
      }
    }

    .detail-info-r {
      flex: 1;
      overflow: hidden;

      .book-title {
        font-size: 18px;
        line-height: 22px;
        max-height: 66px;
        font-weight: 500;
        overflow: hidden;
        color: #000;
        text-overflow: clip;
      }

      .book-author {
        margin-top: 10px;
        font-size: 16px;
        line-height: 18px;
        max-height: 36px;
        overflow: hidden;
        color: #333;
        text-overflow: clip;
      }

      .book-category {
        margin-top: 10px;
        font-size: 14px;
        line-height: 16px;
        max-height: 16px;
        overflow: hidden;
        color: #666;
        text-overflow: clip;
      }
    }
  }
</style>

在src\pages\detail\detail.vue中引入DetailBook,接下来对接图书详情API
在src\api\index.js中新建一个接口函数bookDetail:

export function bookDetail(params) {
  return get(`${API_URL}/book/detail`, params)
}

修改src\pages\detail\detail.vue为如下:

<template>
  <div>
    <DetailBook :book="book"/>
  </div>
</template>

<script>
import DetailBook from '../../components/detail/DetailBook'
import {getStorageSync} from '../../api/wechat'
import {bookDetail} from '../../api'
export default {
  components: {DetailBook},
  computed: {
  },
  data() {
    return {
      book: {}
    }
  },
  mounted() {
    const openId = getStorageSync('openId')
    const { fileName } = this.$route.query
    // 在页面加载时获取图书信息并展示
    if (openId && fileName) {
      bookDetail({ openId, fileName }).then(res => {
        this.book = res.data.data
      })
    }
  },
  methods: {
  }
}
</script>

<style lang="scss" scoped>
</style>

预览:
在这里插入图片描述

六、图书统计组件

显示图书的统计信息

组件名称属性参数用途默认值
DetailStatpropsreaders读者信息[]
readerNum阅读人数0
rankNum评分人数0
rankAvg平均得分0

在src\app.json中引入组件van-rate

  "usingComponents": {
  	...
    "van-rate": "vant-weapp/dist/rate/index"
  }

新建src\components\detail\DetailStat.vue:

<template>
  <div class="detail-stat">
    <div class="detail-stat-l">
      <div class="detail-stat-rate-wrapper">
        <span class="detail-stat-rate">{{rankAvg}}</span>
        <van-rate
          :value="rankAvg"
          :size="16"
          color="#717882"
          void-color="#DEE0E2"
          void-icon="star"
        ></van-rate>
      </div>
      <div class="detail-stat-rate-hint">{{rankNum}}人点评</div>
    </div>
    <div class="detail-stat-r">
      <div class="detail-stat-num-wrapper">
        <span class="detail-stat-num">{{readerNum}}</span>
        次访问
      </div>
      <div class="detail-stat-readers">
        <div
          class="detail-stat-readers-avatar"
          v-for="(reader, index) in readers"
          :key="index"
        >
          <ImageView
            :src="reader.avatarUrl"
            mode="scaleToFill"
            height="100%"
            round
          ></ImageView>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import ImageView from '../base/ImageView'
  export default {
    components: { ImageView },
    props: {
      readers: Array,
      readerNum: Number,
      rankNum: Number,
      rankAvg: Number
    }
  }
</script>

<style lang="scss" scoped>
  .detail-stat {
    display: flex;
    padding: 10px 15px;

    .detail-stat-l {
      flex: 0 0 50%;
      width: 50%;

      .detail-stat-rate-wrapper {
        display: flex;
        align-items: center;

        .detail-stat-rate {
          font-size: 24px;
          color: #5E5E5E;
          margin-right: 10px;
        }
      }

      .detail-stat-rate-hint {
        margin-top: 5px;
        font-size: 11px;
        color: #99A0AA;
      }
    }

    .detail-stat-r {
      flex: 0 0 50%;
      width: 50%;

      .detail-stat-num-wrapper {
        display: flex;
        align-items: center;
        font-size: 12px;
        color: #868686;

        .detail-stat-num {
          font-size: 24px;
          color: #5E5E5E;
          margin-right: 5px;
        }
      }

      .detail-stat-readers {
        display: flex;
        font-size: 12px;
        color: #909090;

        .detail-stat-readers-avatar {
          width: 25px;
          height: 25px;
          margin-left: -8px;
          border-radius: 50%;
          border: 1px solid #ccc;
          box-shadow: 0 4px 4px rgba(0, 0, 0, .3);

          &:first-child {
            margin-left: 0;
          }
        }
      }
    }
  }
</style>

在src\pages\detail\detail.vue中引入组件并配置相关参数:

    <DetailStat
      :readers="book.readers"
      :readerNum="book.readerNum"
      :rankNum="book.rankNum"
      :rankAvg="book.rankAvg"
    />

预览:
在这里插入图片描述

七、图书评分组件

图书评分组件

组件名称属性参数用途默认值
DetailRatepropsrateValue上次评分0
methodsonRateChange评分更改时触发事件(空)

新建src\components\detail\DetailRate.vue:

<template>
  <div class="detail-rate">
    <span class="detail-rate-text">轻点评分</span>
    <div class="detail-rate-wrapper">
      <van-rate
        :value="rateValue"
        :size="25"
        color="#1D89EE"
        void-color="#DEE0E2"
        void-icon="star"
        @change="onRateChange"
      ></van-rate>
    </div>
  </div>
</template>

<script>
  export default {
    props: {
      rateValue: Number
    },
    methods: {
      onRateChange(e) {
        this.$emit('onRateChange', e.mp.detail)
      }
    }
  }
</script>

<style lang="scss" scoped>
  .detail-rate {
    display: flex;
    padding: 10px 0;
    margin: 0 15px;
    align-items: center;
    border-bottom: 1px solid #EBEBEB;

    .detail-rate-text {
      font-size: 14px;
      color: #909090;
    }

    .detail-rate-wrapper {
      flex: 1;
      text-align: right;
    }
  }
</style>

将mounted里面的内容封装成一个方法,并在mounted中调用:

  mounted() {
    this.getBookDetail()
  },
  methods: {
  	...
    getBookDetail() {
      const openId = getStorageSync('openId')
      const { fileName } = this.$route.query
      // 在页面加载时获取图书信息并展示
      if (openId && fileName) {
        bookDetail({ openId, fileName }).then(res => {
          this.book = res.data.data
        })
      }
    }
  }

在src\api\index.js中新建一个接口函数bookRankSave:

export function bookRankSave(params) {
  return get(`${API_URL}/book/rank/save`, params)
}

在src\pages\detail\detail.vue中引入组件并配置相关参数:

    <DetailRate
      :rateValue="book.rateValue"
      @onRateChange="onRateChange"
    />
    onRateChange(value) {
      const openId = getStorageSync('openId')
      const { fileName } = this.$route.query
      bookRankSave({ openId, fileName, rank: value }).then(() => {
        // 评分完成后重新拉取图书信息(总评分会发生改变)
        this.getBookDetail()
      })
    },

预览:
在这里插入图片描述

八、图书目录组件

显示图书目录

组件名称属性参数用途默认值
DetailContentspropscontents图书目录信息[]
methodsreadBook阅读电子书(空)

新建src\components\detail\DetailContents.vue:

<template>
  <div class="detail-contents-wrapper">
<!--不知为啥:上面添加 v-if="!contents"就会不显示-->
<!--    {{!!contents}}这个的值竟然是false-->
    <div class="detail-contents-title">目录</div>
    <div
      class="detail-contents"
      v-for="(item, index) in contents"
      :key="index"
      @click="() => readBook(item.href)"
    >
      <div
        class="detail-contents-label"
        :style="{marginLeft: (15 * item.level) + 'px'}"
      >
        {{item.label}}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    contents: Array
  },
  methods: {
    readBook(nav) {
      this.$emit('readBook', nav)
    }
  }
}
</script>

<style lang="scss" scoped>
  .detail-contents-wrapper {
    padding: 0 0 60px 15px;
    margin-top: 15px;

    .detail-contents-title {
      font-size: 22px;
      color: #333333;
    }

    .detail-contents {
      padding: 20px 15px 20px 0;
      border-bottom: 1px solid #EBEBEB;

      &:last-child {
        border-bottom: none;
      }

      .detail-contents-label {
        font-size: 15px;
        color: #868686;
        line-height: 18px;
        max-height: 18px;
        text-overflow: ellipsis;
        overflow: hidden;
      }
    }
  }
</style>

在src\api\index.js中新建一个接口函数bookContents:

export function bookContents(params) {
  return get(`${API_URL}/book/contents`, params)
}

在src\pages\detail\detail.vue中引入组件并配置相关参数:

    <DetailContents
      :contents="contents"
      @readBook="readBook"
    />
    readBook(href) {
      console.log(href)
    }

在data中新增contents: []用来存放图书目录信息并使用如下方法来调用接口获取图书目录:

    getBookContents() {
      const { fileName } = this.$route.query
      if (fileName) {
        bookContents({ fileName }).then(res => {
          this.contents = res.data.data
        })
      }
    },

这个方法和this.getBookDetail()一起在页面加载时直接调用:

  mounted() {
    this.getBookDetail()
    this.getBookContents()
  },

预览:
在这里插入图片描述

九、图书详情页脚组件

图书详情页脚组件

组件名称属性参数用途默认值
DetailBottompropsisInShelf是否在书架中[]
methodshandleShelf修改书架状态(空)
readBook阅读电子书(空)

新建src\components\detail\DetailBottom.vue:

<template>
  <div class="detail-bottom">
    <div class="detail-btn-wrapper">
      <van-button
        :custom-class="isInShelf ? 'detail-btn-remove' : 'detail-btn-shelf'"
        round
        @click="handleShelf"
      >
        {{isInShelf ? '移出书架' : '加入书架'}}
      </van-button>
    </div>
    <div class="detail-btn-wrapper">
      <van-button
        custom-class="detail-btn-read"
        round
        @click="() => readBook()"
      >
        阅读
      </van-button>
    </div>
  </div>
</template>

<script>
  export default {
    props: {
      isInShelf: Boolean
    },
    methods: {
      handleShelf() {
        this.$emit('handleShelf')
      },
      readBook() {
        this.$emit('readBook')
      }
    }
  }
</script>

<style lang="scss" scoped>
  .detail-bottom {
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 60px;
    background: #fff;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    padding: 0 15px;
    border-top: 1px solid #eee;
    box-shadow: 0 -2px 4px 0 rgba(0,0,0,.1);

    .detail-btn-wrapper {
      flex: 1;
    }
  }
</style>

<style lang="scss">
  .detail-bottom {
    .detail-btn-read {
      width: 100%;
      border: none;
      color: #fff;
      background: #1EA3F5;
      margin-left: 7.5px;
    }

    .detail-btn-shelf {
      width: 100%;
      color: #1EA3F5;
      background: #fff;
      border: 1px solid #1EA3F5;
      margin-right: 7.5px;
    }

    .detail-btn-remove {
      width: 100%;
      color: #F96128;
      background: rgba(255, 175, 155, .3);
      border: 1px solid #FFAF9B;
      margin-right: 7.5px;
    }
  }
</style>

在src\api\index.js中新建一个接口函数bookIsInShelf,用来获取书架上是否有这本书:

export function bookIsInShelf(params) {
  return get(`${API_URL}/book/shelf/get`, params)
}

在src\pages\detail\detail.vue中引入组件并配置相关参数:

    <DetailBottom
      :isInShelf="isInShelf"
    />

在data中新增isInShelf: false用来判断书架上是否有这本书并使用如下方法来调用接口获取这个状态:

	getBookIsInShelf() {
      const openId = getStorageSync('openId')
      const { fileName } = this.$route.query
      if (openId && fileName) {
        bookIsInShelf({ openId, fileName }).then(res => {
          const { data } = res.data
          data.length === 0 ? this.isInShelf = false : this.isInShelf = true
        })
      }
    }

这个方法也是要在页面加载时直接调用:

  mounted() {
    this.getBookDetail()
    this.getBookContents()
    this.getBookIsInShelf()
  },

预览:
在这里插入图片描述
在src\api\index.js中新建一个接口函数bookShelfSave,用来将图书加入书架:

export function bookShelfSave(params) {
  return get(`${API_URL}/book/shelf/save`, {
    shelf: JSON.stringify(params)
  })
}

为DetailBottom组件新增加入书架的方法handleShelf:

    <DetailBottom
      :isInShelf="isInShelf"
      @handleShelf="handleShelf"
    />
    handleShelf() {
      if (!this.isInShelf) {
        const openId = getStorageSync('openId')
        const { fileName } = this.$route.query
        bookShelfSave({openId, fileName}).then(res => {
          // 重新获取加入书架的状态
          this.getBookIsInShelf()
        })
      }
    }

预览:
在这里插入图片描述

这块儿我遇到了一个问题:同这位好友描述:van-button 点击事件不生效_实战问答
暂未解决。。。

在src\api\index.js中新建一个接口函数bookShelfRemove,用来将图书移出书架:

export function bookShelfRemove(params) {
  return get(`${API_URL}/book/shelf/remove`, {
    shelf: JSON.stringify(params)
  })
}

在handleShelf中添加,图书移除操作

	handleShelf() {
      console.log('加入书架la')
      const openId = getStorageSync('openId')
      const { fileName } = this.$route.query
      if (!this.isInShelf) {
        bookShelfSave({ openId, fileName }).then(this.getBookIsInShelf)
      } else {
        const that = this
        mpvue.showModal({
          title: '提示',
          content: `是否确认将《${this.book.title}》移出书架`,
          success(res) {
            if (res.confirm) {
              bookShelfRemove({ openId, fileName }).then(that.getBookIsInShelf)
            }
          }
        })
      }
    }

预览:
在这里插入图片描述


相关接口文档:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序边界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值