VUE音乐播放器学习笔记(3) - vuex + ( router.beforeEach导航钩子:拦截路由导航 ) + ( 不同设备宽度下,让div宽高相等=设备宽度 ) + ( v-bind:style=""绑定样式属性 ) + ( this.$router.push()和this.$router.back() ) + (better-scroll的封装,总结 ) + ( watch 和 vm.$watch 观察者 ) + ( 事件修饰符 和 v-on:click.once=""事件只触发一次 ) + ( .snyc修饰符 )

(1) export 和 export default 的区别

  • export ( import ) 可以有多个,export default ( 默认导出 ) 只能有一个
  • export导出后引用需要加 { } ,export default则不需要

(2) vuex

state.js

功能:状态数据

const state={
    singer:{}
}

export default state

mutation-types.js

功能:放置方法名,或者对mutations做封装

export const SET_SINGER = 'SET_SINGER'

mutations.js

功能:操作state数据

import * as types from './mutation-types'

const matutaions = {
    [types.SET_SINGER] (state,singer){    // singer是传入的参数
        state.singer=singer
    }
}

export default matutaions

getters.js

功能:获取state里的数据,对state数据做映射

export const singer = state => state.singer

actions.js

功能:放异步操作

function findIndex(list, song) {
  return list.findIndex((item) => {
    return item.id === song.id
  })
}

index.js

功能: 入口文件

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'
import createLogger from 'vuex/dist/logger'     // 打印motation的修改纪录

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'
// 调试工具,在dev环境开启,在production环境关闭

export default new Vuex.Store({
  actions,
  getters,
  state,
  mutations,
  strict: debug,
  plugins: debug ? [createLogger()] : []
})

存数据 ( 到 vuex 的 state 中 )

singer.vue



import {mapMutations} from 'vuex'  // 语法糖


    methods: {

      selectSinger(singer) {     // 子组件派发的this.$emit('select',item),这个item就是singer
        this.$router.push({
          path: `/singer/${singer.id}`
        })
        this.setSinger(singer)   

        // 在...mapMutations中取得setSinger方法,singer数据作为参数传给该方法,该方法改变state中的singer

      },
      _getSingerList() {
        getSingerList().then((res) => {
          if (res.code === ERR_OK) {
            this.singers = this._normalizeSinger(res.data.list)
          }
        })
      },

     ...mapMutations({
        setSinger: 'SET_SINGER'    // 映射成一个setSinger方法,对应的是mutation-types.js中的常量
      })

    },

取数据 ( 在 vuex 的 state 中 )

singer-detail.js



import {mapGetters} from 'vuex'

computed: {
      ...mapGetters([      // 扩展到computed中,是一个数组
        'singer'
      ])
    }


(3) router.beforeEach((to, from, next) => { })

导航钩子:作用是拦截导航

http://blog.csdn.net/wenyun_kang/article/details/70987840
https://segmentfault.com/a/1190000011140870
(详细)http://www.jianshu.com/p/2578cc444b70
(详细)http://www.jianshu.com/p/9798e3e63998

(1) 全局的钩子router.beforeEach((to, from, next) => { } )

(2) 路由元信息 meta 字段,标记是否需要检测导航

  • 在router文件夹的index.js中
router/index.js



export default new Router({
  routes: [
    {
      path: '/',
      name: 'home',
      redirect: '/recommend',
      meta: {                       // 路由元信息字段,表示是否需要检测导航,true表示需要检测
        needLogin: true
      }
    },
    {
      path: '/login',
      component: Login
    },
    {
      path: '/recommend',
      name: 'recommend',
      component: Recommend
    },
    {
      path: '/singer',
      name: 'singer',
      component: Singer,
      meta: {
        needLogin: true
      },
      children: [{
        path: ':id',
        component: SingerDetail,
        meta: {
          needLogin: true
        }
      }]
    }
  ]
})


  • 在main.js中
main.js



router.beforeEach((to, from, next) => {
  if (to.meta.needLogin) {       // 如果目标页面需要登陆
    if (window.sessionStorage.data) {   // 但是同时已经登陆过了,有session
      next()     // 直接跳转到目标页面
    } else {
      alert('请先登录')  
      next('/login')     // 否则跳转到登陆页面
    }
  } else {
    next()     // 如果不需要登陆,直接跳转到目标页面
  }
})

  • 在login.js中
login.js



    methods: {
      login() {
        this.$http.post('https://www.easy-mock.com/mock/59d2fed09cabc90bb5e5c287/music/login', {
          phone: `${this.userName}`,
          password: `${this.passName}`
        }).then((response) => {
          console.log(response, 'response')
          console.log(response.data, 'response.data')

          if (response.data.data) {     // 如果提交成功,返回数据存在
            let ses = window.sessionStorage
            let data = JSON.stringify(response.data.data)
            ses.setItem('data', data)    // 设置session,需要以字符串方式存储
            this.$router.push('/')    // 跳转路由
          } else {
            console.log('有错误')
          }
        })
      }
    }

(4) v-bind:style='' '' 绑定样式属性

(1)


 <div class="bg-image" v-bind:style="bgStyle">    // 一般绑定到样式属性上,计算属性

    computed: {
      bgStyle() {
        return {
          'background-image': `url(${this.bgImage})`,
           border: '1px solid red'
        }
        // return `background-image:url(${this.bgImage})`
      }
    }

-----------------------------------------------------------------------
(2)



// 这种方式不提倡,不直观
 <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>  
  data: {  
    activeColor: 'red',  
    fontSize: 30  
  }  

(5) 在不同宽度的设备上,让图片宽度为设备宽度,且高度和宽度相等

  • 注意:这种布局不要在class="bg-image"的div中直接写内容(比如文字,这样不会在div中),需要在里面再加子div,在子div中写内容,子div用绝对定位

<div class="bg-image" v-bind:style="bgStyle" ref="bgImage">
      <!--<div class="bg-image" v-bind:style="{color:red}">1-->
      <!--filter是一个加暗的浮层-->
      <div class="filter"></div>
    </div>



 .bg-image
      position: relative
      width: 100%
      height: 0
      padding-top: 100%
      transform-origin: top
      background-size: cover
      .filter
        position: absolute
        top: 0
        left: 0
        width: 100%
        height: 100%
        background: rgba(7, 17, 27, 0.4)

(6) promise 封装axios请求

test2.js



import axios from 'axios'
// import Vue from 'vue'
// Vue.prototype.$http = axios

export function getSongs() {
  return axios.get('/api/artist/album?id=6452&limit=30').then((response) => {
    return Promise.resolve(response)
  })
}

-----------------------------------------------------------------------

search.vue



  import {getSongs} from 'api/test2.js'
  export default {
    created() {
      this.getData()
    },
    methods: {
      getData() {
        getSongs().then((data) => {
          console.log(data)
        })
      }
    }
  }

----------------------------------------------------------------

上面的/api是使用了代理,如下配置:

config/index.js


    proxyTable:{
      '/api': {
        target: 'http://localhost:3000/',
        changeOrigin: true,
        pathRewrite: {
          '^/api':''
        }
      }
    }

访问 ('/api/artist/album?id=6452&limit=30') 
其实是:http://localhost:3000/api/artist/album?id=6452&limit=30

(7)

this.$router.push() 跳转

this.$router.back() 返回

(1) this.$router.push


    selectSinger(singer) {     // 子组件派发的this.$emit('select',item),这个item就是singer
        this.$router.push({
          path: `/singer/${singer.id}`
        })
        this.setSinger(singer)           
        // 在...mapMutation中取得setSinger方法,singer数据作为参数传给state
      },

--------------------------------------------

(2) this.$router.back()


  <div class="back" v-on:click="back">
      <i class="icon-back"></i>
    </div>

  methods: {
      back() {
        this.$router.back()
      }
    },

(8) watch 观察者

例一:


<script>

var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },

  watch: {
    // 如果 question 发生改变,这个函数就会运行
    question: function (newQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.getAnswer()
    }
  },

  methods: {
    getAnswer() {........}
  }
})

</script>

例二:


<span style="color:#006600;"><div id="app">  
    <input type="text" v-model:value="childrens.name" />  
    <input type="text" v-model:value="lastName" />  
</div>  
  
<script type="text/javascript">     
    var vm = new Vue( {  
        el: '#app',  
        data: {  
            childrens: {  
                name: '小强',  
                age: 20,  
                sex: '男'  
            },  
            tdArray:["1","2"],  
            lastName:"张三"  
        },  
        watch:{  
            childrens:{  
                handler:function(val,oldval){  
                    console.log(val.name)  
                },  
                deep:true//对象内部的属性监听,也叫深度监听  
            },  
            'childrens.name':function(val,oldval){  
                console.log(val+"aaa")  
            },//键路径必须加上引号  
            lastName:function(val,oldval){  
                console.log(this.lastName)  
            }  
        },//以V-model绑定数据时使用的数据变化监测  
    } );  
    vm.$watch("lastName",function(val,oldval){  
        console.log(val)  
    })//主动调用$watch方法来进行数据监测</span>  
</script>  
</body>  

例三:


var v_assetManage_controller = new Vue({  
    el: '.LSee-index',  
    data: {  
        trendQueryTimeCtr: {  
            startTime: '',  
            endTime: ''  
        }  
    },  
    ready: function() {  
        //  
    },  
    methods: {  
        queryTrendData: function(){  
            //do some here  
        }  
    },  
    watch: {  
        'trendQueryTimeCtr.startTime': 'queryTrendData',  
        'trendQueryTimeCtr.endTime': 'queryTrendData'  
    }  
  
});  

(8) vm.$watch( expOrFn, callback, [options] )

  • 参数:
    {string | Function} expOrFn
    {Function | Object} callback
    {Object} [options]
    {boolean} deep // 深度监听,一般用于对象
    {boolean} immediate
    返回值:{Function} unwatch
  • 用法:
    观察 Vue 实例变化的一个表达式或计算属性函数。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。
  • 选项:deep
    为了发现对象内部值的变化,可以在选项参数中指定 deep: true 。注意监听数组的变动不需要这么做。

// 键路径
vm.$watch('a.b.c', function (newVal, oldVal) {
  // 做点什么
})

// 函数
vm.$watch(
  function () {
    return this.a + this.b
  },
  function (newVal, oldVal) {
    // 做点什么
  }
)

(9) better-scroll的封装

  • probeType
    类型:Number
    默认值:0
    可选值:1、2、3
    作用:有时候我们需要知道滚动的位置。
    当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件;
    当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;
    当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。

  • momentum
    类型:Boolean
    默认值:true
    作用:当快速在屏幕上滑动一段距离的时候,会根据滑动的距离和时间计算出动量,并生成滚动动画。设置为 true 则开启动画。
    翻译:momentum是动量的意思

  • refresh
    参数:无
    返回值:无
    作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。


<template>
  <div ref="wrapper">
    <slot></slot>
  </div>
</template>

<script type="text/ecmascript-6">
  import BScroll from 'better-scroll'

  export default {
    props: {
      /**
       * probeType
       * 1 滚动的时候会派发scroll事件,会截流。
       * 2 滚动的时候实时派发scroll事件,不会截流。
       * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
       */
      probeType: {       // 类型
        type: Number,
        default: 1
      },
      click: {         // 是否能点击
        type: Boolean,
        default: true
      },
      listenScroll: {   // 是否派发滚动事件
        type: Boolean,
        default: false
      },
      data: {         // 列表数据
        type: Array,
        default: null
      },
      pullup: {       // 是否派发滚动到底部的事件,用于上拉加载
        type: Boolean,
        default: false
      },
      pulldown: {    // 是否派发顶部下拉的事件,用于下拉刷新
        type: Boolean,
        default: false
      },
      beforeScroll: {    // 是否派发列表滚动开始的事件
        type: Boolean,
        default: false
      },
      refreshDelay: {   // 当数据更新后,刷新scroll的延时。
        type: Number,
        default: 20
      },
      scrollX: {  // 横向滚动
        type: Boolean,
        default: false
      }
    },
    mounted() {
      setTimeout(() => {
        this._initScroll()
      }, 20)
    },
    methods: {
      _initScroll() {
        if (!this.$refs.wrapper) {
          return
        }
        this.scroll = new BScroll(this.$refs.wrapper, {
          probeType: this.probeType,
          click: this.click,
          scrollX: this.scrollX
        })
        // 是否派发滚动事件
        if (this.listenScroll) {
          let me = this
          this.scroll.on('scroll', (pos) => {
            me.$emit('scroll', pos)
          })
        }
        // 是否派发滚动到底部事件,用于上拉加载
        if (this.pullup) {
          this.scroll.on('scrollEnd', () => {
            if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
              this.$emit('scrollToEnd')
            }
          })
        }
        // 是否派发顶部下拉事件,用于下拉刷新
        if (this.pulldown) {
          this.scroll.on('touchend', (pos) => {
            // 下拉动作
            if (pos.y > 50) {
              this.$emit('pulldown')
            }
          })
        }
        // 是否派发列表滚动开始的事件
        if (this.beforeScroll) {
          this.scroll.on('beforeScrollStart', () => {
            this.$emit('beforeScroll')
          })
        }
      },
//      方法代理
      disable() {   // 作用:禁用 better-scroll,DOM 事件(如 touchstart、touchmove、touchend)的回调函数不再响应。
        this.scroll && this.scroll.disable()
      },
      enable() {  // 作用:启用 better-scroll
        this.scroll && this.scroll.enable()
      },
      refresh() {  // 作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。
        this.scroll && this.scroll.refresh()
      },
      scrollTo() { // 作用:滚动到指定的位置
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
      },
      scrollToElement() { // 作用:滚动到指定的目标元素
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      }
//      _watchData() {
//        setTimeout(() => {
//          this.refresh()
//        }, this.refreshDelay)
//      }
    },
    watch: {
      data() {
        setTimeout(() => {
          this.refresh()
        }, this.refreshDelay)
      }
//      'data': '_watchData' //观察data的变化,当data变化是执行_watchData函数,上面的是直接执行
    }
  }
</script>

<style scoped lang="stylus" rel="stylesheet/stylus">

</style>



(10) 事件修饰符

.stop : 阻止冒泡
.prevent : 阻止默认事件
.capture
.self : 指定元素触发(不包括子元素)
.once : 事件只执行一次,特别之处:还能用在组件事件上

ps: 给组件添加原生事件,需要添加.naive

<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件侦听器时使用事件捕获模式 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当事件在该元素本身 (比如不是子元素) 触发时触发回调 -->
<div v-on:click.self="doThat">...</div>

(10) v-on:click.once="" 事件只触发一次

  • 不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。

<!-- 点击事件将只会触发一次 -->

<a v-on:click.once="doThis"></a>


(11) .snyc修饰符


<comp :foo.sync="bar"></comp>

会被扩展为:

<comp :foo="bar" @update:foo="val => bar = val"></comp>

当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:

this.$emit('update:foo', newValue)



       
      // (1) 父组件给子组件comp 传递一个foo同步属性,值是bar
     // (2) 当子组件想要改变foo属性时,由于子组件不能修改父组件传过来的任何数据,所以用.sync修饰符
    // (3)  当子组件想要改变foo属性时,向父组件派发一个'update:foo'事件,newValue为传递的数据
   // (4) 'update:foo'事件将foo的值改为从子组件传过来的newValue值



rank-detail.vue




<sync class="sync" v-bind:bg.sync="bb" v-if="bb"></sync>


data() {
   return {
      bb: true
      }
    }

-----------------------------------------------------------------------------

sync.vue



 <div class="sync" v-on:click="goto" v-if="bb">
    这里是sync组件显示内容
  </div>



<script type="text/ecmascript-6">
  export default {
    props: {
      bb: {
        type: Boolean,
        default: true
      }
    },
    methods: {
      goto() {
        this.$emit('update:bg', false)
      }
    }
  }
</script>

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • 记得罗胖在跨年演讲的国民总时间,现在各个商业现在抢夺的都是人们的时间,就是注意力。 如今商业中有两种方式,一种是浪...
    平静的力量阅读 146评论 0 0
  • “如果你感觉到困惑,请不用担心。这是冬眠后的一些副作用,我将会帮助您适应生活。”就在老王连胡思乱想的力气都没有...
    王十三_8a1f阅读 387评论 0 0
  • 新建文件 新建文件夹
    庞贝船长阅读 109评论 0 0