去哪了网项目总结(移动端)

一、启动项目

1. 安装脚手架和依赖

首先保证你的电脑上有node和npm,版本越新越好

  1. npm install -g vue-cli
    全局安装脚手架工具,安装的时候可以指定版本
  2. vue init webpack myProject
    用webpack工具初始化vue项目,myProject是项目名称,可以自己命名
  3. cd myProject
    进入创建的vue项目目录
  4. npm install
    安装项目所需要的依赖,也就是安装一些必要的插件
  5. npm run dev
    开始运行项目

2. 项目代码介绍

  1. README.md----项目的介绍说明文件
  2. package.json----一些第三方模块的依赖
  3. package-lock.json----确定第三方包的版本,保证团队编程的统一
  4. LICENSE----开源协议的说明(就是你的代码别人可以怎么用,是否可用于商业化等)
  5. index.html----首页的模板文件
  6. .postcssrc.js----对postcss的一个配置文件,postcss会处理我们的css
  7. .gitignore----把不想提交到线上的文件放在这里
  8. eslintrc.js----写代码的规范
  9. .editorconfig----配置了一些编辑器的语法,可以自行添加修改
  10. .babelrc----语法解析器,把我们写的vue单文件组件写法的代码解析成浏览器能够识别的代码
  11. static----静态资源(静态图片或者我们模拟的json数据)
  12. node_modules----我们依赖的第三方包
  13. src---我们项目的源代码
  14. config----我们项目的配置文件

index.js 基础的配置信息
dev.nav.js 开发时的配置信息
prod.nev.js 线上的配置信息

  1. build----我们项目打包的一些webpack的配置内容

3. 多页应用 vs 单页应用

3.1 多页应用

  • 什么是多页应用?

页面每次跳转,后台都会返回一个新的HTML页面

  • 执行步骤:

页面跳转----->返回HTML

  • 优缺点:

优点:

  1. 首屏时间快(请求了第一个HTML页面,就立即展示)
  2. SEO效果好(搜索引擎可以识别HTML中的内容,而我们每个页面都放在HTML中,搜索效果好)

缺点:
页面切换慢(每次跳转都要发一个HTTP请求)

3.2 单页应用

  • 什么是单页应用?

js感知到url的变化,js会动态的清除掉当前页面的内容,把下个页面的内容挂载到当前页面上,页面始终就有一个,路由是由我们前端做了。

  • 执行步骤:

页面跳转----->js渲染

  • 优缺点:

优点:
页面切换快
缺点:
首屏时间稍慢,SEO差(现在可以解决)

4. 项目代码初始化(移动端)

  1. 设置meta标签
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
  2. 引入 reset.css---把不同手机的浏览器的初始化样式做一个统一
  3. 引入border.css---解决移动端1像素边框的问题(因为手机分辨率不同)
  4. 引入fastclick库---解决某些机型点击后延迟300ms的问题。

    怎么引入?

    1. npm install fastclick --save(save是指不管开发和线上都使用)
    2. main.js中引入,import fastClick from "fastclick",然后
      fastClick.attach(document.body),绑定到body上
  5. iconfont矢量图标库建立一个图标仓库

使用方法:

  • 本地引用:
  1. 打开官网->图标管理->我的项目->新建项目->选择所需图标到购物车->添加至新建项目
  2. 将选择好的图标仓库下载到本地
  3. 把所有的字体文件(以svg,eot,ttf,woff等结尾的文件)和iconfont.css文件拿出来,其他的文件没用
  4. 把所有的字体文件放在一起iconfont文件夹(本项目将iconfont文件夹放在了assets里面styles文件夹下)
  5. 修改iconfont.css中的字体路径
  6. main.js中全局引入iconfont.css文件或者在需要字体图标的文件里局部引入iconfont.css文件
  7. 在官网字体所在仓库里把iconfont字体的地址粘贴到代码对应位置,OK

优点:在没有网络的时候,字体图标可以正常显示
缺点:如果想要在仓库里新加图标应用到项目,那么必须重新引入

  • 在线引用:
  1. 在字体仓库下有生成在线链接,点击,生成在线链接,放入项目的css文件夹下面,不要忘了字体的样式也要引入
  2. 在官网字体所在仓库里把iconfont字体的地址粘贴到代码对应位置,OK

优点:添加新的图标的时候,只需要在重新生成一次在线链接,把链接放入项目中 就可以使用新添加的图标啦
缺点:没网的时候不会显示,以file协议打开也不会显示。

二、Home页面

1. Home页面----Header.vue组件

  1. 安装stylus工具(写css)

npm install stylus --save
npm install stylus-loader --save
npm run dev(重新启动项目)

  1. 代码优化

公用样式

  1. 把公用样式放到一个文件里

本项目在assets下styles建了varibles.styl文件,存放公共样式,比如全局背景色$bgColor = ##00bcd4定义在这个文件里

  1. 使用

在要公用样式的组件里面的style里面引入这个文件@import '../../../assets/styles/varibles.styl',然后background: $bgColor

  1. 优点:便于维护和修改

自定义路径
在项目中,我们都知道src可以用@代替,我们也可以自定义一些路径
步骤:
build->webpack.base.conf.js找到resolve配置项,可以自定义

'@': resolve('src'),
'styles': resolve('src/assets/styles'),

现在上面那个引用就可以改成@import '~styles/varibles.styl'
注意:在一个css中引用另一个css文件,如果想使用自定义路径,路径前面必须加上~

~ 是 stylus-loader 到东东,参考 https://github.com/shama/stylus-loader
~styles 表示相对 styles,然后我们在 webpack 配置了 styles 的 alias,就能找到了它的路径了

2. Home页面----Swiper.vue组件

  1. 建立一个index-swiper分支(在真实的开发环境下,每一个独立功能都有一个独立分支)
  • 在githup下建立分支
  • 在终端 git pull---把线上的改变拉到本地
  • git checkout 'index-swiper'---切换分支
  • 新分支开发完成
  • git add . ; git commit -m ; git push (index-swiper)
  • 将新分支的代码合并到主分支
  • git checkout master(切换到主分支)
  • git merge index-swiper(将新分支代码合并到当前分支)
  • git push(提交到线上仓库)
  1. 使用 Vue-Awesome-Swiper轮播插件
    使用方法:参照官网或者 vue-awesome-swiper的使用以及API整理

问题:怎么防止页面抖动?
描述:网速较慢时,轮播图还没加载出来,轮播图所在位置高度为零,下面的元素占据了轮播图的位置,等到轮播图加载出来,就会把其他元素挤下去,页面抖动
注意:轮播图中图片也不要给固定长宽,要用百分比
解决办法:

  1. 在轮播图外面包裹一层固定宽高的盒子(不推荐)

更换图片或者改变图片大小的时候,页面可能会发生错乱

  1. 在轮播图外包裹一层长宽比例以图片一致的盒子
    width: 100%;height: 33%vw

这种方法可以,但是兼容性不太好

  1. 在外面包裹一个盒子,盒子设置如下:(这是普遍的用法BFC)
.wrapper
     overflow hidden
     width 100%
     height 0
     padding-bottom 26.67%
     background #eee
  1. 在vue项目组件中使用子组件(外部组件),怎么去修改子组件的样式?
  1. 去掉scoped,但会污染全局组件

去掉scoped,style样式就不再局限与本组件,变成全局样式,自然会影响到子组件的样式

  1. 混用本地和全局样式
<style>
/* 全局样式 */
</style>
<style scoped>
/* 本地样式 */
</style>
  1. 使用深度作用选择器
    如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可>以使用 >>> 操作符:
<style scoped>
.a >>> .b {
 /* ... */
}
</style>

有些像 SASS 之类的预处理器无法正确解析 >>>。这种情况下你可以用 /deep/操作符取而代之 —— 这是一个 >>> 的别名,同样可以正常工作。

3. Home页面----Icons.vue组件

  1. 轮播时分页

这就需要两次循环,第一次循环把所有的页找出来,然后在循环每一页中的元素

4. Home页面----Recommend.vue组件

  1. 省略号的使用

有时候一句话太长,我们又不想让它换行,但是又得提醒用户后面还有内容,这就需要省略号啦
怎么添加?

  1. 单行文本的溢出
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;

注意:有的时候在部分机型上没有效果,那么就换成多行文本的溢出形式就行,把多行的行数调成1行

  1. 多行文本的溢出 (WebKit)
overflow hidden;
 text-overflow: ellipsis;
 display: -webkit-box; /*将对象作为弹性伸缩盒子模型显示*/
 -webkit-line-clamp: 1; /* 限制在一个块元素显示的文本的行数 */
 -webkit-box-orient: vertical; /* 垂直排列 */
 word-break: break-all;  /* 内容自动换行 */

适用场景:这个属性只合适WebKit浏览器或移动端(绝大部分是WebKit内核的)浏览器

  1. 多行文本的溢出 (利用定位和伪类元素)
p{
   position: relative; 
   width:400px;
   line-height: 20px; 
   max-height: 60px;
   overflow: hidden;
}
p::after{
   content: "..."; 
   position: absolute; 
   bottom: 0; 
   right: 0; 
   padding-left: 40px;
   background: -webkit-linear-gradient(left, transparent, #fff 55%);
   background: -o-linear-gradient(right, transparent, #fff 55%);
   background: -moz-linear-gradient(right, transparent, #fff 55%);
   background: linear-gradient(to right, transparent, #fff 55%);
}

适用场景:文字内容较多,确定文字内容一定会超过容器的,那么选择这种方式不错。但文字未超出行的情况下也会出现省略号,可结合js优化该方法。 效果图

注:

  • 将height设置为line-height的整数倍,防止超出的文字露出。
  • 给p::after添加渐变背景可避免文字只显示一半。
  • 由于ie6-7不显示content内容,所以要添加标签兼容ie6-7(如:<span>…<span/>);兼容ie8需要将::after替换成:after。

可以结合js代码优化:

$(function(){
//获取文本的行高,并获取文本的高度,假设我们规定的行数是五行,那么对超>>>过行数的部分进行限制高度,并加上省略号
  $('p').each(function(i, obj){
       var lineHeight = parseInt($(this).css("line-height"));
       var height = parseInt($(this).height());
       if((height / lineHeight) >3 ){
           $(this).addClass("p-after")
           $(this).css("height","60px");
      }else{
           $(this).removeClass("p-after");
       }
   });
})

5. 使用axios发送Ajax请求

  1. npm install axios --save; 在Home组件中引入import axios from 'axios'
  2. Home页面共有5个组件都需要Ajax获取数据,如果每个组件都发一个请求,那么就会降低页面的性能。
    解决办法:可以在Home页面请求数据一次,然后把请求的数据通过父子组件传值(props)传递给子组件。Ajax请求数据的操作本项目是在mounted钩子函数中完成的,其实在created中也可以,相当于数据初始化。
  3. 本地mock数据

注意:

  • 本地mock的数据要写在static目录下,因为只有static目录下的文件才可以被外部访问到,其他目录下的文件不能被访问,访问时会自动跳转到首页的
  • mock文件夹是本地数据,不想提交到线上,那么就在.gitignore中写入
    static/mock
  • json文件内的元素以,分割,不过最后一个元素不要加,,容易解析错误
  1. 那么我们访问数据时就可以axios.get('/static/mock/index.json')

问题:我们是本地模拟数据,上线前还要修改路径,上线前修改代码是有风险的,不推荐这么做,怎么办?

解决:
config目录下index.js文件中的proxyTable中修改配置(这是由webpack-dev-server工具提供的)

proxyTable: {
     '/api':{
       target: 'http://localhost:8080',//访问的服务器端口地址,默认的,后期如果修改服务器地址,直接改这个
       pathRewrite: {//路径替换
         '^/api': '/static/mock'
       }
     }
   },

三、City页面

1. better-scroll 滚动组件的使用

当 better-scroll 遇见 Vue

  1. 安装使用

npm install better-scroll --save安装
import BScroll from 'better-scroll'在需要滚动的组件中引入

使用
mounted () {
    this.scroll = new BScroll(this.$refs.wrapper)
}
  1. html格式(需要包裹两层)
<div class="wrapper">
 <div class="content">
   <div>...</div>
   <div>...</div>
     ...(需要滚动的元素)
 </div>
</div>

注意:wrapper(父元素)的高度一定要小于content(子元素)的高度才会滚动,否则无法滚动


image.png

2. offsetTop的理解

注意:

  • offsetTop的参考点是:与当前元素相距最近的经过定位(position不为static)的父级元素(offsetParent),并不是我以前认为的body元素
    具体情况如下:
  1. position为fixed时,offsetParent为null,offsettop的值和top相等。此时元素是以视口来定位的。
  2. position非fixed,父级元素无定位(static)时,offsetParent为body。
  3. position非fixed,父级元素有定位时,offsetParent为最近的有定位的父级元素。
  4. body元素,offsetParent为null,offsettop为0 。

3. 通过函数节流可以减少部分操作的执行频率,提高网页的性能

以下代码就是通过一个计时器进行节流,意思是若你刚开始进行这个操作的时候,会延迟16ms在执行;若是在16ms之间你有进行手指滚动操作,会把你当前执行的取消,在开始执行新的操作

 if(this.touchStatus){
                //通过添加计时器对函数进行节流,提高性能
                if(this.timer){
                    clearTimeout(this.timer)
                }
                this.timer = setTimeout(()=>{
                    //const startY = this.$refs['A'][0].offsetTop
                    const touchY = e.touches[0].clientY - 74
                    const index = Math.floor((touchY - this.startY) / 23)
                    if(index >= 0 && index <= this.letters.length){
                        this.bus.$emit('change',this.letters[index])
                    }
                },16)
                
                
            }

4. 移动端touch事件和鼠标事件

移动端的touch click事件的理解+点透
触屏事件与鼠标事件

说明: 在很多情况下,触摸事件和鼠标事件会同时触发,目的是让没有对触摸设备做优化的代码也可以在触摸设备上正常运行。当然,如果你使用了触摸事件,那么可以通过e.preventDefault()来阻止鼠标事件被触发

1. 事件说明

触屏事件:

  • touchstart 触摸开始(手指放在触摸屏上)
  • touchmove 拖动(手指在触摸屏上移动)
  • touchend 触摸结束(手指从触摸屏上移开)
  • touchcancel,是在拖动中断时候触发。

鼠标事件:

  • mouseover 鼠标进入
  • mousemove 鼠标移动
  • mousedown 鼠标按下触发
  • mouseup 鼠标抬起触发
  • click 鼠标点击事件,包括mousedown和mouseup2个过程

触发规则:

在触屏操作后,手指提起的一刹那(即发生touchend后),系统会判断接收到事件的element的内容是否被改变:

  • 如果内容被改变,会解析为touch事件,接下来的click事件都不会触发,
  • 如果内容没有改变,则会解析为click事件,按照mousedown,mouseup,click的顺序触发事件。
    特别需要提到的是,在解析为click事件时,只有再触发一个触屏事件时,才会触发上一个事件的mouseout事件。
    通常click事件官网文档是说会延时200~300ms,不过项目通过引入fastclick已经解决

触发顺序:

  • 移动端,点击一下会触发:
    touchstart->touchend->mouseover->mousedown-> mouseup->click;
  • 移动端,滑动,触发: touchstart->touchmove->touchend;
  • pc端,点击: mouseover->mousemove->mousedown-> mouseup->click;
  • pc端,移动: mouseover->mousemove

执行顺序代码演示:

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #level0 {
        /* width: 500px;
        height: 500px; */
      }
      #level1-0 {
        background: red;
        width: 500px;
        height: 500px;
      }
      #level1-1 {
        background: green;
        width: 500px;
        height: 500px;
      }
    </style>
  </head>
  <body>
    <div id="level0">
      <div id="level1-0">
      </div>
      <div id="level1-1">
      </div> 
    </div>
  </body>
  <script type="text/javascript">
    var level10 = document.getElementById("level1-0");
    level10.addEventListener('touchstart', function(e) {
      console.log(1);
      e.preventDefault()
    });
    level10.addEventListener('touchmove', function(e) {
      console.log(2);
    });
    level10.addEventListener('touchend', function(e) {
      console.log(3);
    });
    level10.onclick = function() {
      console.log(5);
    }
    document.body.onclick = function() {
      console.log('6');
    }
  </script>
</html>
//

在红色区域点击会出现什么效果呢? 出现的是 1 3 5 6, 奇怪了 touchmove 为何不执行,因为我们并没有移动,也就是说,必须触碰到屏幕上面,而且发生了移动动作,touchmove才执行,现在我们触碰到,而且手指稍微动一下,发现输出的效果是, 1 2(+) 3, 其中touchmove 可能触发多次,又奇怪了, click为何不执行, 因为 click执行的条件是 点击, 而且不移动 所以一般情况下,我们可以理解成 touchmove和click是相斥的。
我们知道,当一个用户在点击屏幕的时候,系统会触发touch事件和click事件,touch事件优先处理,touch事件经过 捕获,处理, 冒泡 一系列流程处理完成后, 才回去触发click事件

2. 点透事件

  • 点透发生的条件,
  • A 和 B不是后代继承关系(如果是后代继承关系的话,就直接是冒泡子类的话题了)
  • A发生touch, A touch后立即消失, B事件绑定click
  • A z-index大于B,即A显示在B浮层之上

点透发生的理由:

当手指触摸到屏幕的时候,系统生成两个事件,一个是touch 一个是click,touch先执行,touch执行完成后,A从文档树上面消失了,而且由于移动端click还有延迟200-300ms的关系,当系统要触发click的时候,发现在用户点击的位置上面,目前离用户最近的元素是B,所以就直接把click事件作用在B元素上面了.

点透消除的方法:

  1. e.preventDefault()取消点击事件
level10.addEventListener('touchend', function(e) {
   e.preventDefault();
});
  1. touch操作延迟3s后关闭
setTimeout(() => {
   level10.style.display = 'none';
}, 300);
<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #level0 {
        /* width: 500px;
        height: 500px; */
        position: relative;
      }
      #level1-0 {
        position: absolute;
        z-index: 1;
        background: red;
        width: 500px;
        height: 500px;
      }
      #level1-1 {
        background: green;
        width: 500px;
        height: 500px;
      }
    </style>
  </head>
  <body>
    <div id="level0">
      <div id="level1-0">
      </div>
      <div id="level1-1">
      </div> 
    </div>
  </body>
  <script type="text/javascript">
    var level10 = document.getElementById("level1-0");
    var level11 = document.getElementById("level1-1");
    level10.addEventListener('touchstart', function(e) {
      level10.style.display = 'none';
      e.preventDefault()
    });
    level11.onclick = function() {
      console.log('level11莫名被点击了');
    }
  </script>
</html>

5. vue中eventbus使用的那些坑

参考:vue中使用eventbus踩过的坑
Vue的钩子函数
声明:在已经用到vue-router并且跨页面(跨路由)传值的情景,最好用vuex,不要用eventbus,因为在页面跳转时,新页面组件的加载和旧页面组件的销毁顺序不好把握。
本项目我就是Home页面和City页面使用的bus总线的方式传值,出现的问题和解决方案:
最初代码:

这些代码在同一个页面没有路由跳转的组件间传值是没有问题的,但是,不同页面间传值就有问题啦

//City页面
methods: {
    handleClickChange (city) {
        this.bus.$emit('cityChange',city)
        localStorage.city = city
        this.currentCity = city
    }
}
//Home页面
methods: {
        handleCity (city) {
        this.city = city
        console.log(city)
        localStorage.city = city
    }
},
mounted () {
    this.bus.$on('cityChange',this.handleCity)
},

问题:

  1. 问什么第一次触发Home页面的on事件没有执行?
  2. 后面再次触发时Home页面的on事件就开始执行,并且on事件的执行次数一次次的增加,就像没有撤销一样?

问题一解决:

因为vue-router切换时,先加载新组件,但是在新组件渲染完成还没有挂载前,销毁旧组件,在挂载新组件

切换时顺序:
新组件: beforeCreate
新组件: created
新组件: beforeMount
旧组件: beforeDestroy
旧组件: destroy
新组件: mounted
解决方案:
在City页面(旧组件)的$emit事件写在beforeDestroy,destroy这两个生命周期钩子函数中,Home页面(新组件)的$on事件写在beforeCreate,created,beforeMount中,才能够正确传值

改正后代码:

//City页面
destroyed () {
    this.bus.$emit('cityChange',this.currentCity)
}
//Home页面
methods: {
    handleCity (city) {
        this.city = city
        console.log(city)
        localStorage.city = city
    }
},
created () {
    this.bus.$on('cityChange',this.handleCity)
},

问题二解决:
两个页面都要写上

beforeDestroy () {
    bus.$off('get', this.handleCity)
},

就是说,这个$on事件是不会自动清楚销毁的,需要我们手动来销毁

6. vuex的引入和使用

  1. 引入:
  • npm install vuex --save 安装
  • src->store->index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
   state: {
      city: '北京'
   },
   actions: {
      cityChange (ctx,city) {
           // console.log(city)
           ctx.commit('change',city)
       }
   },
   mutations: {
       change (state,city) {
           state.city = city
       }
   }
})
``
  • import store from './store'main.js中引入,然后注册到实例对象中
  1. vuex中mapState的使用

mapState的作用就是把vuex的state里面的数据映射到组件中,可以让组件直接使用,不用再去store中调用,减少代码的复杂度
本项目的使用:

<script>
import { mapState } from 'vuex'
export default {
    computed: {
        ...mapState(['city']) //把state中的city属性值映射到该组件计算属性的city上
    }
}
</script>

所以,现在代码中this.$store.state.city === this.city

  1. vuex中mapMutations的使用
import { mapState, mapMutations } from 'vuex'
methods: {
    handleCityChange (city) {
        // this.$store.commit('cityChange',city)
        this.cityChange(city)
        this.$router.push('/')
    },
    ...mapMutations(['cityChange'])//把mutations中的cityChange方法映射到该组件cityChange方法上
},

7. localStorage的使用

本项目使用vuex之后可以改变状态,但是刷新之后数据又变成初始值,怎样才能让数据保持改变后的状态呢?
使用localStorage,将传过来的值用localStorage本地缓存起来,与cookie的效果有点像,但cookie是服务器端的
把上面的代码这样写,就能够保留状态

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        city: localStorage.city || '北京'
    },
    mutations: {
        cityChange (state,city) {
            state.city = city
            localStorage.city= city
        }
    }
})

注意:有些浏览器因为用户关闭了本地存储或使用了隐身模式,使用localStorage浏览器可能会抛出异常,解决办法如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let defaultCity = '北京'
try {
    if(localStorage.city){
        defaultCity = localStorage.city
    }
}catch (e) {}
export default new Vuex.Store({
    state: {
        city: defaultCity
    },
    mutations: {
        cityChange (state,city) {
            state.city = city
            try{
                localStorage.city = city
            }catch (e) {} 
        }
    }
})

就是把所有的localStorage.city都放在try catch
但是try catch是什么东西?
try catch是一种错误处理机制,try...catch语句将能引发错误的代码放在try块中,并且对应一个响应,然后有异常被抛出
try语句允许我们定义在执行时进行错误测试的代码块。
catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。
finally 语句在 try 和 catch 之后无论有无异常都会执行。
注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。

8. keep-alive的使用

页面不使用keep-alive,页面每次跳转都要重新加载一次页面,非常影响性能
如下图所示:


解决办法:在页面显示位置外面包裹一层keep-alive
如下:

<keep-alive>
  <router-view/>
</keep-alive>

效果如下图:
页面只加载了一次,通过keep-alive,把页面保存到内存里面,再次加载只需要去内存里面去取就行

laalllll.gif

问题:如果说城市不变,页面确实不必再次加载,但是如果城市改变了,页面内容不变就出问题啦,怎么解决呢?
keep-alive提供了一个生命周期钩子函数activated,将这个钩子写在页面里,只要这个页面显示,不管是不是从内存中取得,它都会执行,所以我们可以在这个钩子里进行当前城市与上一个城市进行对比,若城市不同,就发一个Ajax请求
代码如下:

methods: {
      getHomeInfo(){
        axios.get('/api/index.json?city=' +this.city )//请求数据时带上城市信息参数
        .then(this.getHomeInfoSucc)
      },
      getHomeInfoSucc(res){
        console.log(res)
        res = res.data
        if(res.ret && res.data){
          this.swiperList = res.data.swiperList
          this.iconList = res.data.iconList
          this.recommendList = res.data.recommendList
          this.weekendList = res.data.weekendList
        }
      }
    },
    mounted () {
        this.lastCity = this.city//将最初的城市信息保存下来
        this.getHomeInfo()
    },
    activated () {
        if(this.lastCity !== this.city){
            this.lastCity = this.city//为上一个城市重新赋值
            this.getHomeInfo()
        }
    }

四、Detail页面

1. Detail页面---banner组件

1.1 <router-link>问题

router-link在页面中会被渲染成a标签,所以会改变router-link内包裹内容的颜色,怎么解决?

  1. 直接给router-link包裹的内容重新定义颜色,不要在使用继承来的颜色
  2. 直接用router-link替换掉内部包裹的标签,用tag指向原来的标签
        <ul>
            <router-link to="/detail">
                <li>
                      点我
                </li>
            </router-link>  
        </ul>

        <ul>
            <router-link to="/detail" tag='li'>                
                      点我
            </router-link>  
        </ul>

1.2 动态路由的使用

配置路由时:

   {
      path: '/detail/:id',
      name: 'detail',
      component: Detail
    }

页面中要把信息传过来

<ul>
    <router-link 
    :to='"/detail" + item.id'
    tag='li'>                
              点我
    </router-link>  
</ul>

2. Detail页面---公用Gallery组件

  • 如果一个组件会被多个页面或组件引用,就应该设为公用组件,放在一起
    本项目在src目录下设了common目录存放公用组件,而且在buildwebpack.base.conj.jsresolve里面修改了common的引用路径,以后可以直接引用common
    'common': resolve('src/common'),
  • 图片画廊公用组件其实就是一组图片的拖动轮播组件,所以要使用vue-awesome-swiper插件来实现,而该插件又是基于Swiper3的,所以插件的一些配置项可以在Swiper3中找答案
  • 一个注意点:如果要修改轮播图的样式或者位置,最好在外层在包裹一层div,不要直接对组件直接进行修改,要不很容易出现一些莫名其妙的错误

3. Detail页面---Header组件

对全局事件的解绑:
本组件监听了一个全局滚动事件,在任何一个页面或组件滚动都会触发本操作,这明显是不利的,我们要的是只在本页面滚动触发本操作而已

methods: {
        handleScroll () {
            console.log('scroll')
            const top = document.documentElement.scrollTop
            if(top >= 45){
                this.headerShow = true
                const opacity = top / 120
                this.styleOpacity = {opacity: opacity}
            }else{
                this.headerShow = false
            }
        }
    },
    activated () {
        window.addEventListener('scroll',this.handleScroll)
    },

1234.gif

所以我们要进行解绑:
利用keep-alive提供的另一个生命周期钩子deactivated,就是页面消失的时候该函数执行

deactivated () {
        window.removeEventListener('scroll',this.handleScroll)
 }

4. Detail页面---List组件

递归组件的使用:我们经常看到的列表分为一级,二级等几个级别的列表,这就是递归组件做出来的。

组件的名字(name)用处:

在递归组件中,递归组件要引用自己的时候,组件的名字就是代表的这个组件,直接用名字就可以啦

list: [{
          title: '成人票',
          children: [{
              title: '成人三馆联票',
              children: [{
                  title: '成人三馆联票 - 清明上河园'
              }]
          },{
              title: '成人五馆联票'
          },]
      },]
<div  v-for="(item,index) of list" :key="index">
    <div class="list-item">
        <span class="mp-ticketype-ticket"></span>
        {{item.title}}
    </div>
    <div v-if="item.children" class="item-children">
        <detail-list :list="item.children"></detail-list>//递归组件使用自己,这是组件名字的用处
    </div>
</div>

5. Detail页面---ajax请求

  1. 请求时传参
    拼接的形式,如果参数比较多,就不适用啦
axios.get('/api/index.json?city=' +this.city )

第二种:

 axios.get('/api/detail.json',{
           params: {
           id: this.$route.params.id
      }
 })
  1. keep-alive可以本地缓存,如果我们不想要一个页面或组件使用缓存,那么可以:
<keep-alive exclude="Detail">
   <router-view/>
</keep-alive>

现在name=Detail的这个组件就不再使用缓存啦

注意:因为这个页面不使用缓存啦,相当与keep-alive没有作用啦,那么它提供的两个钩子activated,deactivated就没有作用啦,因为不使用缓存,页面每次都要重新加载,那么mounted,unmounted这两个钩子开始每次执行都要加载,可以代替上面的两个钩子

  1. 组件中的name有什么用
  1. 使用递归组件的时候可以通过名字对组件自身进行引用
  2. 对某个页面取消缓存的时候也要用到name
  3. chrome浏览器中的vue 插件中的dom结构列表也要用到name
    image.png
  1. 拖动行为是相互影响的

比如第一个页面你滚动到底部,那么进入第二个页面你会发现,页面页面滚动位置与第一个页面位置同步,我们想要进入一个新页面,页面位置在顶端,该怎么办?

vue-router中的滚动行为中有一个scrollBehavior,在路由routes下面加上这段代码就可以解决问题,使页面切换的时候始终回到最顶部

scrollBehavior (to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition
      } else {
        return { x: 0, y: 0 }
      }
    }

5. Detail页面---在banner中加入FadeAnimation.vue动画组件

新建一个公用的FadeAnimation.vue动画组件

<template>
    <transition>
        <slot></slot>
    </transition>
</template>
<script>
export default {
    name: 'FadeAnimation'
}
</script>
<style lang="stylus" scoped>
    .v-enter, .v-leave-to {
        opacity: 0
    }
    .v-enter-active, .v-leave-active {
        transition opacity 1s
    }
</style>

使用:

<fade-animation>
      <CommonGallary 
      :imgs = 'this.detailData.gallaryImgs' 
       v-show="showGallary"
       @close="close"
       ></CommonGallary>
</fade-animation>
<script>
import FadeAnimation from 'common/fade/FadeAnimation'
export default {
    name: 'DetailBanner',
    components: {
        FadeAnimation
    },
}
</script>

五、项目的联调、测试、与发布上线

1. 项目的联调

本次服务器是使用的小型本地PHP服务器

  1. 根据网上地址安装XMAPP,https://www.apachefriends.org/zh_cn/download.html
  2. XMAPP默认的项目地址是放在 C:\xampp\htdocs 下,里面默认有很多个文件(可以都删除掉),创建一个api文件夹,里面放\static\mock里的3个json文件(老师并没有在视频里演示创建这个文件夹)。
  3. 浏览器访问http://localhost/api/index.json 如果有json数据,就表示服务器搭建成功了。
  4. 按照视频里的操作修改\config\index.js 文件,修改代理接口
  5. 运行npm run dev,然后访问http://localhost:8080,数据就能正常访问了。
proxyTable: {
      '/api':{
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api': '/static/mock'
        }
      }
    },
//修改后
proxyTable: {
      '/api':{
        target: 'http://localhost:80',//这是本地服务器地址(:80可以省略,因为默认端口就是80)
//
    },

本项目是前台后台数据都在本地的一个项目,较为简单,服务器地址常见的内网IP,外网域名都可以

2. 项目的真机测试

  1. 获取本机的IP地址,在终端中输入
    ipconfig(windows); ifconfig(ios)
  2. 通过ip地址启动项目,但是你会发现启动不了,因为vue项目是通过webpack dev server启动项目的,而webpack dev server默认不支持IP地址访问
  3. 修改package.jsondev配置项,就可以用IP地址来访问啦
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
//修改后
"dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
  1. 让手机和电脑在同一个局域网下(连的是同一个WiFi),手机就可以通过IP地址访问这个项目
  2. 手机上的bug1(手机拖动右侧的字母表的时候,整个屏幕跟着滑动)
    image.png

    解决:添加事件修饰符prevent,阻止该事件的默认行为
    @touchstart.prevent = 'handleTouchStart'
  3. 手机上的bug2
    一些低版本的安卓机会出现白屏的问题
    原因一:手机不支持promise等一些ES6的语法

解决: 安装一个包,这个包会自动判断手机是否支持ES6的新特性,如不支持,会自动下载
npm install babel-polyfill --save
import 'babel-polyfill'(在main.js中引入)

原因二:webpack-dev-server的问题,打包上线后就会解决

3. 项目的打包上线

  1. npm run build生成一个dist目录,这个目录的内容是我们代码的一个封装和压缩,是我们要上线的内容
  2. 把这个包交给后端,放到后端服务器的根路径下
  3. 就可以直接访问后端服务器的地址访问网站啦 http://192.168.43.229/#/
  4. 如果说我们不想把打包的代码直接扔到服务器的根目录下,想放在根目录的一个project目录下,该怎么做呢
  • 首先,修改config/index.js下的build配置项里的打包路径,把'/'->'/project'
  • 再次打包,运行npm run build
  • 可以把打包生成的dist目录下的内容放入服务器根目录下的project目录
  • 访问http://192.168.43.229/project/#/

4. 异步组件实现按需加载

  1. 首先看一下打包生成的dist目录


    image.png

    以map结尾的文件,是开始的时候调试的时候使用,意义并不是特别大
    css文件,所有页面用到的css文件全部打包到这里
    manifest.js:webpack打包生成的所有配置文件
    vendor.js:各个页面,组件公用的代码
    app.js:各个页面的业务逻辑代码

  2. 我们为什么要使用异步组件?


    image.png

    三个js文件,前两个几乎没法修改
    每次加载页面,就要把所有的业务逻辑代码加载一下,若是项目比较大,势必会大大影响页面的初始加载速度,若是app.js小于1mb以下,就没有必要使用异步组件啦

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

推荐阅读更多精彩内容

  • vue概述 在官方文档中,有一句话对Vue的定位说的很明确:Vue.js 的核心是一个允许采用简洁的模板语法来声明...
    li4065阅读 7,176评论 0 25
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,137评论 0 1
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,048评论 0 2
  • 元代景德镇瓶、罐之类器型,采用分段制胎,然后再用胎泥粘合而成,粘接处器表往往突起,给人以不平之感,外壁接痕经打磨...
    骨玩阅读 231评论 0 0
  • 晚上睡前跟女儿说我小时候爬山的故事,小家伙非常感兴趣,时不时插上两句:“如果我看到那种陡的地方我会嗖直接滑下来或者...
    幸福_娟阅读 217评论 2 2