序言
刚来杭州的时候,是两个人一起来的。后来一个回家当老师,一个留在了杭州。想起那时,在慕课买的第一课程,囫囵吞枣的看完了。勉强才找到了一份工作,今天看起来,其实还是有点难过的。唉,其实人生不就是这样吗?还是要感谢思否还有类似掘金这种前端社区,帮助我解决不少问题,直到今天,也算是入门了。现在回过头来看看,这个临时抱佛脚的启蒙项目,真的还有不少的收获。漏了不少知识点,现在捡起来,总结一下。
-
滚动视图的位置缓存
/* *@to,from 路由元对象。@savedPosition 触发 H5 下的 popstate,go 导航 (通过浏览器的 前进 / 后退 按钮触发) 时才可用。*/ scrollBehavior(to, from, savedPosition) { return { x: 0, y: 0 } }
- 这个功能只在 HTML5 history 模式下可以使用。
- {selector: string, offset? : { x: number, y: number}} (offset 只在 2.6.0+ 支持)
-
路由导航,可以模拟 hash 滚动到锚点
if (to.hash) { return {selector: to.hash} }
-
kekeepAlive 结合,返回上次停留的位置(常用), 缓存页面数据,当且两个 keepAlive 以上的页面才可以触发。
if (savedPosition) {return savedPosition} else {if (from.meta.keepAlive) {from.meta.savedPosition = document.body.scrollTop;} return {x: 0, y: to.meta.savedPosition ||0} }
-
动态路由匹配参数
routes:[ { path: '/detail/:id', name: 'Detail', component: Detail } ] this.$router.push({name:'Detail',params:{id:id}});
- 路由模式为 history 模式,服务器需要做到对前端路由的全匹配。可以参考官网
- 类似项目中的购物车,商品详情页等,可以做到直观符合逻辑,params 可以作为路径参数传递,前提是做好路由匹配。
-
mock 数据与跨域代理
- 建立 mock 数据,最好建立在 public 文件之内
-
配置代理,直接指向本地 mock 文件夹. 项目中可以配置后台接口或者映射地址,
devServer: { proxy: { '/api': { // 到时候修改这个地方 target: 'http://localhost:8080', changeOrigin: true, // 后台接口地址重定向,真实对接的时候下面的替换到路径,测试环境和本地。pathRewrite: {'^/api': '/mock'} } } }
-
封装组件
awosome-swiper 为例,github 上的一个开源 swiper 组件,公共画廊组件的拆分,其实所用到数据是从上述本地 mock 中得来的。
通用画廊组件
// CommonGallary.vue
// 已在 main.js 中引用 awosome-swiper(use)
<template>
<div class="container" @click="handleGallaryClick">
<dir class="wrapper">
<swiper :options="swiperOptions">
<swiper-slide v-for="(item, index) of imgs" :key="index">
<img :src="item" class="gallary-img" />
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</dir>
</div>
</template>
<script>
export default {
name: "CommonGallary",
data() {
return { // 分页参数配置
swiperOptions: {
pagination: ".swiper-pagination",
paginationType: "fraction",
// 监视到 dom 元素发生了变化,就会自我刷新一下,防止 swiper 滑动失败
observer: true,
observeParents: true
}
};
},
props: {
imgs: { // 子组件接收图片数组
type: Array,
default() {return [];
}
}
},
methods: {handleGallaryClick() {this.$emit("close"); // 子组件向上触发事件,父组件进行监听
}
}
};
</script>
过度动画通用组件
// FadeAnimation.vue
// 已在 main.js 中引用 awosome-swiper(use)
<template>
<div>
<transition>
<!-- 里面的内容由父组件来传入 dom -->
<slot></slot>
</transition>
</div>
</template>
<script>
export default {name: 'FadeAnimation'}
</script>
<style lang="stylus" scoped>
/* 开始进入和首次离开 透明度为 0 动画进入激活 - 动画失活 1s 按照透明度过度 */
.v-enter, .v-leave-to
opacity 0
.v-enter-active , .v-leave-active
transition opacity 1s
</style>
画廊父组件 DetailBanner.uve
// DetailBanner.vue
//
<template>
<div>
<div class="banner" @click="handleBannerClick">
<img class="banner-img" :src="bannerImg" alt="bannerImg">
<div class="banner-info">
<div class="banner-title">
{{this.sightName}}
</div>
<div class="banner-number">
<span class='iconfont banner-icon'></span>
{{this.bannerImgs.length}}
</div>
</div>
</div>
// 引入过度动画和公用画廊
<fade-animation>
<common-gallary :imgs="bannerImgs" v-show="showGallary" @close="handleGallaryClose"></common-gallary>
</fade-animation>
</div>
</template>
<script>
import CommonGallary from 'common/gallary/Gallary'
import FadeAnimation from 'common/fade/FadeAnimation'
export default {
name: 'DetailBanner',
props: {
sightName: String, // prop 传参
bannerImg: String,
bannerImgs: Array
},
components: {
CommonGallary,
FadeAnimation
},
data() {
return {
showGallary: false,
imgs: ["http://img1.qunarzz.com/sight/p0/201404/23/04b92c99462687fa1ba45c1b5ba4ad77.jpg_800x800_70debc93.jpg", "http://img1.qunarzz.com/sight/p0/1709/76/7691528bc7d7ad3ca3.img.png_800x800_9ef05ee7.png"]
}
},
methods: {
// 触发显示和监听关闭
handleBannerClick () {this.showGallary = true},
handleGallaryClose () {this.showGallary = false}
}
}
</script>
到这里的就基本上差不多了。
-
事件监听与吸顶过渡
Header 头部记录
<template>
<div>
<router-link
to="/"
tag="div"
class="header-abs"
v-show="showAbs"
>
<div class='iconfont header-abs-back'></div>
</router-link>
<div
class="header-fixed"
v-show="!showAbs"
:style="opacityStyle"
>
景点详情
<router-link to="/">
<div class='iconfont header-fixed-back'></div>
</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'DetailHeader',
data() {
return {
showAbs: true,
opacityStyle: {opacity: 0}
}
},
methods: {handleScroll () {console.log('scroll')
// 获取当前滚动高度
const top = document.documentElement.scrollTop
if(top > 60) {
// 当滚动高度超过一个 header 的高度时,触发吸顶过度动画
let opacity = top / 140 // 透明度随高度改变 最终为 1
opacity = opacity > 1 ? 1 : opacity
this.opacityStyle = {opacity} // 键值相等
this.showAbs = false // 箭头返回关闭
}
else{this.showAbs = true}
// console.log(document.documentElement.scrollTop)
}
},
// 也可以用 destory 进行销毁
activated () {
// 这里是绑定在了全局 window 对象中,太过于消耗性能,需要全局事件的解绑
window.addEventListener('scroll', this.handleScroll)
},
// 页面即将被隐藏,或者页面即将被替换掉时
deactivated () {window.removeEventListener('scroll', this.handleScroll)
},
}
</script>
之后再接着写吧,有点累了。不知道说什么好了,公司开的就剩我一个前端了,可能我也要走了, 我可不想这么轻易的滚回家。