词条
目前市面上还没有一个Vue 2.0 的高级教学,都是一些基础的入门课程,你很难找到一个基于Vue.js的复杂应用的教学, 但是,我们为你准备了这门独一无二的Vue 2.0 高级实战课程
src简单的介绍
入口文件
import 'babel-polyfill' //写在第一位import Vue from 'vue'import App from './App'import router from './router'import fastclick from 'fastclick'import VueLazyload from 'vue-lazyload'import store from './store'import 'common/stylus/index.styl'/* eslint-disable no-unused-vars */// import vConsole from 'vconsole'fastclick.attach(document.body)Vue.use(VueLazyload, { loading: require('common/image/default.png') //传一个默认参数})/* eslint-disable no-new */new Vue({ el: '#app', router, store, render: h => h(App)})
babel-polyfill是es6底层铺垫即支持一些API,比如promise
Tab页面
推荐 歌手 排行 搜索
`router-link默认是a标签,我们通过tag指定为div .router-link-active这个class是组件自带的`
APP.vue
仔细的看一下引入的组件Tab以及一个布局方式
jsonp的封装
import originJsonp from 'jsonp' //jsonp 结合promise 封装export default function jsonp(url, data, option) { url += (url.indexOf('?') < 0 ? '?' : '&') + param(data) return new Promise((resolve, reject) => { originJsonp(url, option, (err, data) => { if (!err) { resolve(data) } else { reject(err) } }) })}export function param(data) { let url = '' for (var k in data) { let value = data[k] !== undefined ? data[k] : '' url += '&' + k + '=' + encodeURIComponent(value) //视频代码 //url += `&${k}=${encodeURIComponent(value)}` es6语法 } return url ? url.substring(1) : ''}
重点关注一下URL的拼接可以用到项目中
API/recommend.js 使用jsonp 调取轮播图的数据
import jsonp from 'common/js/jsonp'import {commonParams, options} from './config'export function getRecommend() { const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg' const data = Object.assign({}, commonParams, { //assign es6语法 platform: 'h5', uin: 0, needNewCode: 1 }) return jsonp(url, data, options)}
用到了es6对象的合并方法Object.assign
config.js
export const commonParams = { g_tk: 1928093487, inCharset: 'utf-8', outCharset: 'utf-8', notice: 0, format: 'jsonp'}export const options = { param: 'jsonpCallback'}export const ERR_OK = 0
定义一些公共参数,不用每次再去重写
components/recommend.vue 在组件中调用接口
`这里用到了slider组件以及slot的知识,也遇到了一个坑,因为数据响应 必须确定有数据v-if="recommends.length"才能保证插槽的正确显示`
export default { data() { return { recommends: [] } }, created() { this._getRecommend() }, methods: { _getRecommend() { getRecommend().then((res) => { if (res.code === ERR_OK) { this.recommends = res.data.slider } }) } }, components: { Slider } }
热门歌单推荐
![]()
在这里没有用jsonp而是用了axios,是因为接口有host、referer校验不得使用后端代理接口的方式去处理
bulid目录下dev-server.js处理代理
require('./check-versions')()var config = require('../config')if (!process.env.NODE_ENV) { process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)}var opn = require('opn')var path = require('path')var express = require('express')var webpack = require('webpack')var proxyMiddleware = require('http-proxy-middleware')var webpackConfig = require('./webpack.dev.conf')var axios = require('axios') //第一步// default port where dev server listens for incoming trafficvar port = process.env.PORT || config.dev.port// automatically open browser, if not set will be falsevar autoOpenBrowser = !!config.dev.autoOpenBrowser// Define HTTP proxies to your custom API backend// https://github.com/chimurai/http-proxy-middlewarevar proxyTable = config.dev.proxyTablevar app = express()var apiRoutes = express.Router() //以下是后端代理接口 第二步apiRoutes.get('/getDiscList', function (req, res) { var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' axios.get(url, { headers: { referer: 'https://c.y.qq.com/', host: 'c.y.qq.com' }, params: req.query }).then((response) => { res.json(response.data) //输出到浏览器的res }).catch((e) => { console.log(e) })})apiRoutes.get('/lyric', function (req, res) { //这是另一个接口下节将用到 var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg' axios.get(url, { headers: { referer: 'https://c.y.qq.com/', host: 'c.y.qq.com' }, params: req.query }).then((response) => { var ret = response.data if (typeof ret === 'string') { var reg = /^\w+\(({[^()]+})\)$/ var matches = ret.match(reg) if (matches) { ret = JSON.parse(matches[1]) } } res.json(ret) }).catch((e) => { console.log(e) })})app.use('/api', apiRoutes) //最后一步var compiler = webpack(webpackConfig)var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, quiet: true})var hotMiddleware = require('webpack-hot-middleware')(compiler, { log: () => {}})// force page reload when html-webpack-plugin template changescompiler.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() })})// proxy api requestsObject.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(options.filter || context, options))})// handle fallback for HTML5 history APIapp.use(require('connect-history-api-fallback')())// serve webpack bundle outputapp.use(devMiddleware)// enable hot-reload and state-preserving// compilation error displayapp.use(hotMiddleware)// serve pure static assetsvar staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)app.use(staticPath, express.static('./static'))var uri = 'http://localhost:' + portvar _resolvevar readyPromise = new Promise(resolve => { _resolve = resolve})console.log('> Starting dev server...')devMiddleware.waitUntilValid(() => { console.log('> Listening at ' + uri + '\n') // when env is testing, don't need open it if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { opn(uri) } _resolve()})var server = app.listen(port)module.exports = { ready: readyPromise, close: () => { server.close() }}
API/recommend.js 使用jsonp 调取热门歌单的数据
export function getDiscList() { const url = '/api/getDiscList' const data = Object.assign({}, commonParams, { platform: 'yqq', hostUin: 0, sin: 0, ein: 29, sortId: 5, needNewCode: 0, categoryId: 10000000, rnd: Math.random(), format: 'json' }) return axios.get(url, { params: data }).then((res) => { return Promise.resolve(res.data) })}
接下来开发推荐页面滚动列表--因为很多页面都支持滚动,所以抽出来一个公用组件Scroll.vue
recommend.vue
可能会遇到一个问题,初始化后不能滚动,是因为高度的问题,所以给img加了一个方法,这里提到了vuex的使用,那怎么给vuex提交数据细心的同学可能会发现↓↓↓↓↓
热门歌单推荐
![]()
接下来是歌手页面,由于考虑到二级路由要跳到歌手详情,所以抽出一个独立组件listview.vue,涉及到数据结构处理、类的创建、es6的字符拼接、数组map方法、自定义data属性获取方法的封装
{ {group.title}}
{ {item.name}}
- { {item}}
{ {fixedTitle}}
singer.vue
引入listview组件,有一个20毫秒的定时器,关键在于左右联动的思路很重要,以及关于diff的处理增强用户体验
歌手详情页,为了组件重用抽出来一个music-list.vue,在此基础又抽出来一个song-list.vue,用到了v-html来转义字符、计算属性里返回对象的某几个key比如只传入name或者头像、mapGetters获取vuex的数据
{ {song.name}}
{ {getDesc(song)}}
随机播放全部
下面是父组件歌手详情,封装了一个createSong的类,可在源码中查看提高了代码的重用性、扩展性因为是面向对象的方式
播放器内置组件 player.vue,通过actions的方法--selectPlay,在此组件拿到currentSong,这里再重点说一下mutations和它的type要做到命名一致,nutations本质就是函数,第一个参数是state第二个参数是要修改的对象值
player组件定义到了app.vue,因为它不属于某一个页面是全局的,mapgetters是一个数组,多次批量修改mutation就要用到actions
重点是动画的过度效果,结合钩子函数实现飞入飞出动画,用到了开源动画库,create-key-animation
音乐播放事件togglePlaying,因为播放的暂停开始要调用audio的方法,可能会出现拿不到元素报错,这是用到了nextTic延时函数,添加class可以用到计算属性,歌曲的前进后退通过currentIndex,有一个小问题,暂停后切换到下一首歌要自动播放,快速点击的时候结合 ready err方法避免快速点击页面报错
条形进度条,通过audio获取可以读写的当前播放时间,将其时间戳转为时分秒格式,通过_pad给秒位前补零,做到与设计图一致,定义基础组件progress-bar,事件拖动和点击滚动条的交互实现,也就是说拖动无非就是三个事件,start move end,拖动开始前加一个开关表示初始化完成,如果拖动前是暂停状态,拖动后再让其播放
圆形进度条,用到了SVG再通过两个circle实现,完全可以应用到实际工作中
播放模式,用到util里面的shuttle函数把数组打乱,用到es6的findindex函数,由于要实时改变currentSong,父组件监听事件会被触发所以做了一个判断,如果id相同什么都不错,因为这个时候还没触发事件
![]()
![]()
{ {line.txt}}
{ {format(currentTime)}} { {format(currentSong.duration)}}![]()