App.vue
和常规一样只包含#app标签,再加上渲染命令router-view,但是这里增加了底部导航栏 NavBottomView, css格式文件为index.css。需要注意的是启用了watch属性来坚挺路由的变化,并通过路由变化来设置底部导航栏是否显示。
watch: {
$route(to,from){
if(to.path.indexOf('detail')!==-1){
this.$store.dispatch('hideNav');
console.log(to.path.indexOf('detail'),"-----");
}else{
this.$store.dispatch('showNav');
console.log(to.path.indexOf('detail'),"==========");
}
if(to.path === '/cart' || to.path === '/search' || to.path === '/login' || to.path === '/register'){
this.$store.dispatch('hideNav');
}
}
},
main.js
导入 vue-router,axios, router.config,lazyload等,加载全局css 文件。
分类主页面
目前只有一级分类,考虑到流行电商App都不搞成树状菜单,所以考虑把支持2级分类,第一级分类在左边以菜单栏方式显示,2级分类显示在右边,如果再点击右边的二级分类项目,会直接搜出该二级分类下的产品列表。
第1章到第三章:完成商城骨架的搭建。
使用命令添加如下插件
npm install fastclick --save
dependency:
bable-runtime
fastclick:无延时300毫秒插件
dev-dependency:
类型样式插件:stylus,stylus-loader
babel-polyfill
第4章:
4.1 Jsonp解决跨域问题
4.2 解决vue2.9.2版本没有dev-server.js问题
第5章 歌手列表页
5.4 Listview基于Scroll组件实现,在此组件基础上增加data属性并由外部传入,然后在Listview组件中加入v-for循环把Title和列表渲染出来即可。在Singer.vue里直接导入Listview组件并传入data数据即可;其次列表图片的懒加载,直接在Listview组件的img更改为v-lazy即可解决图片懒加载。
5.5 右侧快速入口。1、计算属性定义shortcutList得到title首字母为元素的数组。2、添加事件,首先点击事件,
<div class="list-shortcut" @touchstart.stop.prevent="onShortcutTouchStart" @touchmove.stop.prevent="onShortcutTouchMove"
@touchend.stop>
在Scroll里增加:ScrollTo和ScrollToElement方法。
scrollTo() {
this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
},
scrollToElement() {
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
}
在Listview
onShortcutTouchStart(e) {
let anchorIndex = getData(e.target, 'index')
let firstTouch = e.touches[0]
this.touch.y1 = firstTouch.pageY
this.touch.anchorIndex = anchorIndex
this._scrollTo(anchorIndex)
},
3、滑动支持:
onShortcutTouchMove(e) {
let firstTouch = e.touches[0]
this.touch.y2 = firstTouch.pageY
let delta = (this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT | 0
let anchorIndex = parseInt(this.touch.anchorIndex) + delta
this._scrollTo(anchorIndex)
},
4. 高亮显示字母
在Scroll增加一个Props来控制是否要监听滚动事件:
listenScroll: {
type: Boolean,
default: false
},
在_intiScroll方法里增加一段代码来得到列表滚动到的位置
if (this.listenScroll) {
let me = this
this.scroll.on('scroll', (pos) => {
me.$emit('scroll', pos)
})
}
这样就可以在外面监听得到scroll事件.
<scroll @scroll="scroll"
:listen-scroll="listenScroll"
:probe-type="probeType"
:data="data"
class="listview"
ref="listview">
添加一个scroll方法
scroll(pos) {
this.scrollY = pos.y
},
增加一个高度计算方法
先增加一个Watch
watch: { data() { setTimeout(() => { this._calculateHeight() }, 20) },
方法
_calculateHeight() { this.listHeight = [] const list = this.$refs.listGroup let height = 0 this.listHeight.push(height) for (let i = 0; i < list.length; i++) { let item = list[i] height += item.clientHeight this.listHeight.push(height) } },
在观察事件里,增加如下代码
watch: { data() { setTimeout(() => { this._calculateHeight() }, 20) }, scrollY(newY) { const listHeight = this.listHeight // 当滚动到顶部,newY>0 if (newY > 0) { this.currentIndex = 0 return } // 在中间部分滚动 for (let i = 0; i < listHeight.length - 1; i++) { let height1 = listHeight[i] let height2 = listHeight[i + 1] if (-newY >= height1 && -newY < height2) { this.currentIndex = i this.diff = height2 + newY return } } // 当滚动到底部,且-newY大于最后一个元素的上限 this.currentIndex = listHeight.length - 2 },
修改Prop-type=3
让快速接口能够知道ScrollY
_scrollTo(index) { if (!index && index !== 0) { return } if (index < 0) { index = 0 } else if (index > this.listHeight.length - 2) { index = this.listHeight.length - 2 } this.scrollY = -this.listHeight[index] this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0) }
Song-list
第6章
6.1 设计稿部分讲解
6.2 子路由 新建singer-detail子组件,并在router/index.js里增加路由
{ path: '/singer', name: 'Singer', component: Singer, children: [ { path: ':id', component: SingerDetail } ] },
并在Singer组件增加route-view进行挂接
<template> <div class="singer" ref="singer"> <list-view :data="singers" ref="list"></list-view> <router-view></router-view> </div> </template>
因为是基于list-view实现点击列表项跳到歌手详情页面,所以,需要在list-view组件增加点击事件
<ul> <li v-for="(group,index) in data" :key='index' class="list-group" ref="listGroup"> <h2 class="list-group-title">{{group.title}}</h2> <uL> <li @click="selectItem(item)" v-for="(item,index) in group.items" :key='index' class="list-group-item"> <img class="avatar" v-lazy="item.avatar"> <span class="name">{{item.name}}</span> </li> </uL> </li> </ul>
methods: { selectItem(item) { this.$emit('select', item) },
只是派发被点击事件,所以要做Singer组件中添加监听事件并处理
<template> <div class="singer" ref="singer"> <list-view @select="selectSinger" :data="singers" ref="list"></list-view> <router-view></router-view> </div> </template>
实现selectSinger
methods: { handlePlaylist(playlist) { const bottom = playlist.length > 0 ? '60px' : '' this.$refs.singer.style.bottom = bottom this.$refs.list.refresh() }, selectSinger(singer) { this.$router.push({ path: `/singer/${singer.id}` }) },
从而实现了跳转。
实现转场动画
实现Vuex状态管理,建立store目录创建一整套标准文件,
import Vue from 'vue' import Vuex from 'vuex' import * as actions from './actions' import * as getters from './getters' import state from './state' import mutations from './mutations' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ actions, getters, state, mutations, strict: debug })
在main.js导入store,使之生效
import 'babel-polyfill' import Vue from 'vue' import App from './App' import router from './router' import store from './store' import VueLazyload from 'vue-lazyload' import fastclick from 'fastclick' import 'common/stylus/index.styl' Vue.config.productionTip = false fastclick.attach(document.body) Vue.use(VueLazyload, { loading: require('common/image/default.png') }) /* eslint-disable no-new */ new Vue({ el: '#app', render: h => h(App), store, router })
在Singer组件里设置刚才定义的singer属性
import {mapMutations} from 'vuex'
...mapMutations({ setSinger: 'SET_SINGER' })
selectSinger(singer) { this.$router.push({ path: `/singer/${singer.id}` }) this.setSinger(singer) },
在singer-detail组件里去取
<script> import {mapGetters} from 'vuex' export default { name: 'singer-detail', computed: { ...mapGetters([ 'singer' ]) } } </script>
6.6 数据处理和封装
首先在common下建song.js
export default class Song { constructor({id, mid, singer, name, album, duration, image, url}) { this.id = id this.mid = mid this.singer = singer this.name = name this.album = album this.duration = duration this.image = image this.url = url } }
api/singer.js增加
export function getSingerDetail(singerId) { const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg' const data = Object.assign({}, commonParams, { hostUin: 0, needNewCode: 0, platform: 'yqq', order: 'listen', begin: 0, num: 80, songstatus: 1, singermid: singerId }) return jsonp(url, data, options) }
这一节讲课中缺失了getDetail部分,把这部分放在一起singer-detail组件代码
<template> <transition name="slide"> <div class="singer-detail"></div> </transition> </template> <script> import {mapGetters} from 'vuex' import {getSingerDetail} from 'api/singer' import {ERR_OK} from 'api/config' import {createSong} from 'common/js/Song' export default { name: 'singer-detail', data() { return { songs: [] } }, computed: { ...mapGetters([ 'singer' ]) }, created() { this._getDetail() console.log(this.singer) }, methods: { _getDetail() { if (!this.singer.id) { this.$router.push('/singer') return } getSingerDetail(this.singer.id).then((res) => { if (res.code === ERR_OK) { this.songs = this._normalizeSongs(res.data.list) console.log(this.songs) } }) }, _normalizeSongs(list) { let ret = [] list.forEach((item) => { let {musicData} = item if (musicData.songid && musicData.albummid) { ret.push(createSong(musicData)) } }) return ret } } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> @import "~common/stylus/variable" .singer-detail position: fixed z-index : 100 top: 0 left: 0 right: 0 bottom : 0 background : $color-background .slide-enter-active,.slide-leave-active transition : all 0.3s .slide-enter,.slide-leave-to transform : translate3d(100%,0,0) </style>
至此歌手详情页面的路由配置、状态管理、数据定义和处理、数据抓取就完成了,剩下的就是把这些数据显示到界面上。
6.8 歌手详情组件,采取定义一个music-list组件,挂接到singer-detail组件上
SingerDetail
|-- music-list
|-- Song-list
<template> <div class="music-list"> <div class="back"> <i class="icon-back"></i> </div> <h1 class="title" v-html="title"></h1> <div class="bg-image" :style="bgStyle"> <div class="filter"></div> </div> <scroll :data="songs" class="list"> <div class="song-list-wrapper"> <song-list :songs="songs"></song-list> </div> </scroll> </div> </template>
这样挂接以后,界面song-list会把整个界面占满了
mounted() { this.$refs.list.$el.style.top = `${this.$refs.bgImage.clientHeight}px` }