一、学习重点
二、图书详情视觉稿
http://www.youbaobao.xyz/mpvue-design/preview/#artboard9
三、搜索页面组件结构图
四、搜索页面组件示意图
新建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)
}
五、图书信息组件
图书详情中的图书信息组件
组件名称 | 属性 | 参数 | 用途 | 默认值 |
---|---|---|---|---|
DetailBook | props | book | 图书信息 | {} |
新建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>
预览:
六、图书统计组件
显示图书的统计信息
组件名称 | 属性 | 参数 | 用途 | 默认值 |
---|---|---|---|---|
DetailStat | props | readers | 读者信息 | [] |
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"
/>
预览:
七、图书评分组件
图书评分组件
组件名称 | 属性 | 参数 | 用途 | 默认值 |
---|---|---|---|---|
DetailRate | props | rateValue | 上次评分 | 0 |
methods | onRateChange | 评分更改时触发事件 | (空) |
新建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()
})
},
预览:
八、图书目录组件
显示图书目录
组件名称 | 属性 | 参数 | 用途 | 默认值 |
---|---|---|---|---|
DetailContents | props | contents | 图书目录信息 | [] |
methods | readBook | 阅读电子书 | (空) |
新建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()
},
预览:
九、图书详情页脚组件
图书详情页脚组件
组件名称 | 属性 | 参数 | 用途 | 默认值 |
---|---|---|---|---|
DetailBottom | props | isInShelf | 是否在书架中 | [] |
methods | handleShelf | 修改书架状态 | (空) | |
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)
}
}
})
}
}
预览:
相关接口文档: