一、安装
先是配公钥,然后本地与远程同步...一系列操作
$git status 查看新增加进来的文件以及代码改动
$ git add . 将本地的代码推到git本地缓存中
$ git commit -m 'project initialized' 将本地缓存中的内容提交到本地的空库中
-m是留一条信息
$git push 推送代码
二、关于多页面应用/多页面应用
1.多页面应用
(1).每次点击一个不同页面,后台就直接返回回来一整个页面
(2).seo优化:在搜索引擎中,网页排名,只能识别到html文件中的内容,js中的内容识别不到
(3)缺点:每次跳转都需要发送一次http请求,所以切换慢
2.单页面应用
(1).一进来加载一个首页,每次点击其他页,是通过js将原来的页清除,用js进行新的渲染
(2).单页面首次需要发一个http请求,同时还需要发一个js请求,两个请求都回来了,才会展示首屏
(3).缺点:在百度,谷歌搜索排名差
vue开发中,服务器端渲染就可解决单页面开发中的问题
三、vue移动端的基本配置
1.reset.css重置样式文件,上网搜就行
2.移动端1px边框问题,各种手机不同 border.css
3.300ms点击延迟现象 fastclick 第三方模块 --save 即不管是开发还是打包上线都要用的模块用--save
四、正式进入项目的开发----首页和城市页
1.首先安装stylus stylus-loader (对于stylus、scss、less的区别自己先下去补功课)
2.注意移动端用rem,根据reset.css中html的font-size去换算 ,我们这里1rem = 50px;
3.关于图标库的使用方法
4.特别要注意的是每次修改完webpack的配置必须要重启服务器,自己常常因为这个找半天错误也看不出来
3.首页--swiper开发
(1)接下来进行企业级开发,我们在码云上面建一个新的分支,每次开发一个新功能都新建一个新的分支,最后保证这个分支不错误,最后将这个分支合并到总分支master上
$git pull 将线上的index-swiper分支拉到自己的本地
$git checkout index-swiper 将本地原来的master分支切换成index-swiper分支
(2)按照github上vue-aswsome-swiper文档进行安装使用Swiper插件
(3)有一点需要注意,那就是加入.wrapper并设置样式是为了在3G网下,图片没有加载出来,无法将这个容器撑开,导致下面的内容置顶,然后等图片加载出来,下面的内容在弹回到原来应该的位置上,会有一次抖动
(4)项目完成后
以下常规操作,将index-swiper分支上的内容推送到index-swiper分支上
$git add . 推送到本地缓存
$git commit -m 'change' 提交本地缓存
$git push 将本地的代码推送到远程
接下来将index-swiper分支上的内容推到master分支上
$git checkout master 切换到本地master分支上
$git merge origin/index-swiper 将swiper分支上的内容推到master分支上
$git push 将合并后的master分支推动到远程仓库
3.首页--icons开发
还是利用swiper插件
需要注意的点是关于过多图标时如何进行分页
//注意这里要对swiper-slide根据页数进行循环,即一个slide一个slide的切换
<swiper :options="swiperOption">
<swiper-slide v-for="(page,index) of pages" :key="index">
<div class="icon"
v-for="item of page"
:key="item.id">
<div class="icon-img">
<img class="icon-img-content" :src="item.imgUrl">
</div>
<p class="icon-desc">{{item.desc}}</p>
</div>
</swiper-slide>
</swiper>
//将所有的图片分配到他应该在的页中
computed:{
pages(){
const pages = [];
this.iconList.forEach((item,index) => {
const page = Math.floor(index / 8 );
if(!pages[page]){
pages[page] = []
}
pages[page].push(item);
})
return pages;
}
}
但是要注意计算属性中的pages名字要与返回出来的值的名字相同
将代码与master合并,并push远程
4.首页--后台数据获取(ajax 用axios实现)
1.pull拉下来的代码都是从master分支上拉下来的,将不同的分支合并 git merge xxx(分支名字)
2.利用 npm install axios --save 及生命周期钩子mounted()配合 获取后台数据
3.在没有后台数据的情况下,我们进行后台数据模拟,进行配置
(1).static中的文件是可以在网页中显示的,即在网址栏可以直接搜到,而其他文件不可以查看
(2).在.gitignore中写入的文件不会提交的到线上,所以讲ststic/mock文件写到这里面
(3).但只能在本地这么做,要是发布上线的话,获取的数据必须是axios.get('/api/index.json'),所以将这个路径下的api请求都转到对ststic/mock文件夹下
(4).使用webpack代理配置文件 config/index.js文件夹下
proxyTable: {
'/api':{
target:'http://localhost:8081',
'^/api':'/static/mock'
} pathRewrite:{
}
}
开发过程中的小bug为什么一上来幻灯片事从第四张开始呢,因为在为获取后台数据之前,对应的数据是个空数组,所以直接渲染最后一张即第四张,所以要做一个v-if判断,使数据加载了之后再渲染幻灯片组件
还要注意不要把逻辑代码放在结构中,如需计算一些值课放在属性值计算函数中
5.城市页--路由配置问题
在router/index.js文件夹下进行配置
原理:在需要点击跳转页面的元素上加<router-link to='/city'>,其实是向这个页面进行跳转,然后index.js中的文件import City from '@/pages/city/City'是寻找这个单页面组件渲染到页面上
6.城市页--页面整体样式的开发以及axios获取后端数据(还是按照以前的方法在static文件夹下放上json文件)
(1)在开发header和search中用到了box-sizing:border-box,假如给他的width设置成100%其实是包括边框和padding占到100%,而box-sizing:content-box实际上是内容区域就占到了100%不包括边框和padding
(2)一个书写的规范,经常会在一些dom上边在嵌套一个dom,这样做的好处是好调整布局,非常方便
(3)注意一点清除浮动的问题(高度塌陷)看一下list.vue中的样式代码
(4)我们这里不用网页自带的滚动条了,用一个第三方库 npm install better-scroll --save,用滑动更加流畅,同时也有了上下拖拽的效果,等等
(5)注意一下对象的循环和数组的循环,循环的都是什么(item,key)of cities :key='key'对象的键;
(item,index)of cities :key='index'数组的下标
5.城市页--关于右边的A-Z点击后左边切换到特定的位置,实现组件的联动 city-components分支上的代码
首先采取的是 子组件---》父组件----》子组件 的方式来实现两个子组件的传参问题
右侧子组件获取到点击的字母通过$emit传给父组件,父组件再将传过来的值给子组件 list
function(e){...}这里的e代表的是事件,e.target代表的是整个dom元素
接下来的内容是我没有接触过得开发,要好好理解一下,即右边的字母表鼠标在上边滑动时,左边也跟着滑动到相应的位置,需要好好理解:
(1)touchstart,touchmove,touchend是HTML5的新特性,触摸事件,即触摸刚开始,触摸一直移动,触摸结束
(2)我们一开始给了一个标识符touchStatus:false,当刚开始触摸时这个值设为true,当这个值为true了下边的touchmove函数才能执行
(3)这里的client是事件发生时,鼠标距离浏览器的可视区域的X、Y轴的位置,不包含滚动条的区域的部分。就算是页面进行了滚动,鼠标的坐标值还是参考可视区域的。即在屏幕中显示的那部分页面的最顶端的距离
offsetLeft和offsetTop事件源元素相对于父节点的偏移的像素值。即距离包裹他的div的值,一般在静止状态下获取
//Alaphbet.vue字母列表组件
handleTouchMove(e){
//注意这里offset和client的用法
if(this.touchStatus){
const startY = this.$refs['A'][0].offsetTop;
const touchY = e.touches[0].clientY
-79;//79即上边header和search的高度
// 求出当前滑动到的位置的字母下标
const index = Math.floor((touchY - startY)/20);
if(index >= 0 && index <= this.letters.length){
this.$emit('change',this.letters[index]);
}
}
}
最后将这个改变的字母派发出去,传给父组件,父组件接收到,在传递给list.vue,list.vue有一个watch()侦听器,一旦letter变化,便会出发better-scroll的方法滑动到对应的字母列表上
//list.vue
watch:{
// 一旦点击字母letter就会变化,监听到了他变化就出发事件
letter(letter){
if(this.letter){
const element = this.$refs[this.letter][0];
this.scroll.scrollToElement(element);
}
}
},
6.城市页--性能优化
你每滑动一次鼠标,就要触发一次handleTouchMove,很频繁,A距离上边的值一直是固定的,但是你每执行一次函数,你就要执行一次A距离的获取,我们利用一个生命周期钩子函数
data(){
return{
touchStatus:false,//设置一个标示位
startY:0
}
},
updated(){
this.startY = this.$refs['A'][0].offsetTop;
}
原理是,实际上,我们在渲染Aplaet.vue页面上时,一开始数据都是空的,只有当ajax获取到数据之后,数据进行了更新,生命周期钩子updated自动执行,再次进行渲染
函数节流-----当我们在右边的A-Z滑动时,函数执行的额频率相当高,即每动一小下下他都执行一次,所以我们规定在16ms内又一次滑动到其他位置那上一次的这个函数就不执行了,将定时器时间设置为16ms,在16ms内在次出发事件就将上一次的定时器清除掉,这样就降低了函数的执行频率,timer:null,在拖动的过程中明显感觉左边轮动的不是刷刷刷了
handleTouchMove(e){
//注意这里offset和client的用法
if(this.touchStatus){
if(this.timer){
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
const touchY = e.touches[0].clientY - 79;//79即上边header和search的高度
// 求出当前滑动到的位置的字母下标
const index = Math.floor((touchY - this.startY)/20);
if(index >= 0 && index <= this.letters.length){
this.$emit('change',this.letters[index]);
}
},16)//延迟16ms在执行
}
},
接下来就提交一下代码到git仓库中
7.城市页--搜索逻辑代码开发多看一下这部分的代码
实现的功能是在搜索框输入数据的时候相关的城市被选择出来,利用<input type="text" class="search-input" v-model="keyword" placeholder="输入城市名或拼音" /> v-model双向数据绑定
注意的是,运用好v-show来判断每一个div什么时候显示,什么时候不显示,此处也用了一次函数节流,不是每输入一个字母便搜索一次,而是100ms之内在搜索就清除上次的定时器
watch:{
keyword(){
if(this.timer){
clearTimeout(this.timer);
}
this.timer = setTimeout( () => {
const result = [];
// 这里循环的i为键名
for(let i in this.cities){
// cities[i]数组,对数组的每一项(对象)进行循环
// value代表数组的每一项
this.cities[i].forEach((value) => {
if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
result.push(value)
}
})
}
this.list = result;
},100);
}
},
8.城市页--vuex和localStorage的使用多看一下这部分的代码
首先弄明白vuex是怎么工作的(仔细阅读文档)
states----》vue components存储池向组件中传参,vue components---》actions----》mutations组件修改值反馈到存储池中
虚线部分相当于一个公共的存储池
states:静态数据存储
actions异步数据;
mutations:同步的数据;
getters:类似于组件中的computed,利用states的值进行计算,然后进行dom渲染
module:对负载的states、actions、mutations进行拆分
没有异步获取数据的时候actions是可以省略的
//list.vue中的按钮点击时
methods:{
handleCityClick(city){
this.$store.dispatch('changeCity',city);
this.$router.push('/');
}
},
//让存储池获取点击的那个数据
export default {
// ctx是执行上下文,通过这个ctx拿到接下来去执行的commit方法
changeCity(ctx,city){
ctx.commit('changeCity',city);
}
}
//接着传递
export default {
changeCity(state,city){
//state指的是所有公用的数据
state.city = city;
//实现下次刷新也能保留上次的城市记录
try {
localStorage.city = city;
}catch(e){}
}
}
利用HTML5新特性localStorage保证下一次刷新或者进来还是上次存贮的位置(城市)
路由跳转,类似于js中的location.href vue中用this.$router.push(路径)
try-catch为了防止有的用户关闭本地存贮,导致页面出错的存在
mapState:将store仓库中的值直接映射到计算属性中,用this.city 代替this.$store.state.city,两种写法
import {mapState} from 'vuex'
computed:{
...mapState(['city'])
//第二种
...mapState({currentCity:'city'})
}
8.首页城市页--网页性能优化
在做城市点击的时候我们会发现,没点击一次城市主页就会发送一次ajax请求(index.json)显然这样的性能特别差,我们利用vue内置的标签<keep-alive></keep-alive>包裹在入口文件App.vue的router-link外层,表示已经发送过请求的页面被存在内存中,就不在发ajax请求了
但是我么发现,这样还是有错误,因为每一次点不同的城市应该请求不同的内容加载,axios.get('/api/index.json?city=' + this.city) .then(this.getHomeInfoSucc) city用store仓库获取,然后在keep-alive使用后会有一个生命周期钩子activated,在里边改变请求的city,用一个变量lastCity保存最后一次请求的页面,如果不同就ajax请求一次
五、详情页开发
上来不用说了,肯定是路由的配置
page/recommend.vue
<router-link
tag="li"
class="item"
v-for="item of recommendList"
:key="item.id"
:to="'/detail/'+item.id"
>
//router/index.js
routes: [
{
path:'/detail/:id',
name:'Detail',
component:Detail
}
],
//detail.vue
getDetailInfo(){
//从路由跳转那里获得的id被传递到router/index.js的:id中,然后在从:id中提取这个id
// 第一种写法
// axios.get('/api/detail.json?id=' + this.$route.params.id)
// 第二种写法
axios.get('api/detail.json',{
params:{
id:this.$route.params.id
}
}).then(this.handleGetDataSucc)
},
1.详情页--关于banner和gallary的开发
(1)由于项目中很多地方都会用到gallary所以我们吧他封装成一个公共组件,这个组件利用的是swiper插件
(2)在分页器哪里,刚引入时不显示,我们这时候查看源码没问题,就要逐级向上查找:
首先将分页器的位置进行调整 bottom:-1rem
之后我们发现还是不行,我们逐级向上的时候发现.swiper-container中有over-flow:hidden所以将其修改 .container >>> .swiper-container overflow:inherit
(3)当刷新进来发现gallary不能正确的显示了,这是因为一开始gallary隐藏,当gallary被显示的时候swiper计算宽度会出现问题,一旦有这种问题就要想到“刷新”这一解决方法,所以在optionSwiper参数中加上observeParents/observer他们的意思是只要元素本元素或者父元素有改动便刷新一次
2.详情页--关于detail-header的显示和隐藏以及动画渐隐渐现
(1)利用状态值showBack/ !showBack来显示和隐藏导航,当二者对立显示时采用这种方法更好,不要用两个变量控制了
(2)利用生命周期钩子activated监听鼠标滑动,一旦页面展示即执行这个钩子,可以看一下里面坚挺的具体代码
(3)动画渐隐渐现,用动态样式<div :style="opacityStyle">来改变这个值实现透明度的渐变
3.详情页--解绑问题(一定要记住解绑,很重要)
这是一个关键点,好多时候都会因为没有解绑带来很多bug,我们在做详情页的导航时,给了一个监听鼠标滚动事件,这个是全局的监听 window.addEventListener('scroll', this.handlScroll); 这回导致其他页面在滚动时也会被监听,所以我们来接触这个监听,deactivated生命周期钩子,是在即将离开页面时执行的,一旦关闭页面,他就执行了,所以给一个移除监听事件window.removeEventListener('scroll', this.handlScroll);
activated(){
// 相当于js中的window.onscroll
//这里是一个全局的滚动,别的页面也会应用行,所以要解决这个问题,让他只在header滚动的时候生效,别的组件不生效
window.addEventListener('scroll', this.handlScroll);
},
//当页面即将隐藏时(结束时)这个生命周期钩子执行
deactivated(){
window.removeEventListener('scroll', this.handlScroll);
}
4.详情页--列表部分的递归组件
可以利用组件的名字,自己调用自己,所以上一次的自己就成了一个父组件,所以在递归的时候二级标题传进去的是 :list="list",像父组件向子组件传参那样使用
5.详情页--使用ajax获取数据
axios的两种写法,还有这里的获取路由中的id值
问题:用<keep-alive>标签,我们每次加载的页面都被缓存了下来,但是,我们的详情页有不同的id,如果这样做,查看不同id的详情页的时候就不能再发送ajax了,而是直接调缓存了所以解决办法:
(1)像之前的跳转不同城市按钮主页那样,在activated这个生命周期钩子中,没切换一次id发一个ajax请求
(2)App.vue中标签<keep-alive exclude="Detail"> exclude中为组件名字,表示这个组件不会缓存在内存中,所以每点一次发一次请求
6.详情页--关于vue-router的滚动问题
开发文档中有定义,每一次进入页面,页面时从哪个位置开始展示,router/index.js中设置
6.详情页--封装一个公共的动画插件---fadeAnimation
六、测试与上线
1.前后端联调
npm run start 找到 localhost:8080/#/ 实际上找到的是前端服务器
启动自己的后端服务器Wamp,将之前项目static/mock/json数据放进 后端服务器根目录/api/文件夹下
配置文件config/index.js
//原来
proxyTable: {
'/api':{
target:'http://localhost:8080',
pathRewrite:{
'^/api':'/static/mock'
}
}
}
//上线后改为后端服务器,然后直接寻找后端服务器的api文件即可
proxyTable: {
'/api':{
target:'http://localhost:80'
//可省略成'target:'http://localhost'用http协议,用80端口,可以省略80端口
}
}
webpack配置项改变重新启动服务器(前端服务器)网页依旧能能请求到正确的json文件
优点:vue项目的前后端联调就不在需要fiddler这种抓包工具了
2.真机调试---用手机
在cmd中找到自己本机的ip地址
并且修改webpack配置 package.json中的内容,添加 --host 0.0.0.0
"dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
便可通过ip地址访问网页了http://10.255.118.100:8080/#/city
在同一局域网下,手机变可以访问该网页了,便会发现以下问题:
(1)在手机中我们发现一个问题,右侧字母列表不能拖动了,所以解决这个bug:Alphabet.vue文件中,给touchStart一个阻止默认行为 @touchstart.prevent="handleTouchStart"
(2)若手机浏览器不支持promise就会造成白屏的情况所以安装一个第三方包
npm install babel-polyfill --save 然后main.js中引入
3.打包上线
运行 npm run build 然后就将源代码进行打包编译,生成一个dist文件(里面有static和index.html两个文件),该dist文件夹下的代码就是要上线的代码,将这两个文件放在后端服务器根路径上就可以了
若要放到后端特定的文件夹下,例如放在根路径的project文件夹下,就要修改配置项config/index.js
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
//修改这里
assetsPublicPath: '/project'
在重新npm run build 然后将那两个文件放在根路径的project即可,访问后端服务器localhost/project/#/ 即可正确显示网页了