第六章 旅游网站首页开发
首页导航栏
根据设计稿完成页面的布局:
在正式编写代码之前,先来介绍一下stylus
这是一种与less、sass
一样的功能,可以帮助我们快速完成css代码的编写。请使用一下命令进行安装:
npm install stylus --savenpm install stylus-loader --save
开始编写header
页面:
<template><div class="header"><div class="header-left">返回</div><div class="header-input">输入城市景点/游玩/主题</div><div class="header-right">城市</div></div></template><script>export default {name: 'HomeHeader'}</script><!-- 要使用stylus使用lang标识,只想对当前组件有效使用scoped --><style lang="stylus" scoped>.headerdisplay: flexcolor: #fffbackground: #00bcd4line-height: .86rem.header-leftwidth: .64remfloat: left.header-inputflex: 1background: #fffmargin-top: .12remheight: .64remline-height: .64remmargin-left: .2remborder-radius: .05remcolor: #ccc.header-rightwidth: 1.24remfloat:righttext-align: center</style>
如果页面出现顶部有空白的情况,解决办法是在App.vue中的style
中设置内边距和外边距的值为0,一般情况下不会有出现,除非你真是出现了。
<style>*{padding: 0;margin: 0;}</style>
现在我们向把“返回”换成一个图标的形式,就会用到下一节要说的内容。
iconfont的使用和代码优化
首先我们进入iconfont官网,没有账号的先进行注册,注册好了记得新建一个项目(travel)。
如果你已经注册好,点击图标库>官方图标库>选择任意一个,找到我们的需要的图标并且添加到购物车之中>点击右边的购物车并点击添加至项目>然后下载到本地
这时我们会得到一个包,我们只需要用到其中几个文件就可以。我们把需要用到的文件拷贝到我们项目中。
我们可以看到图中,我把除了.css
的的文件放在了style文件夹中,把其它类型的文件放在了新建的iconfont文件夹中。
这时我们在编辑器中打开iconfont.css
文件,我们需要修改一下它所使用到的文件的路径
下面的这一部分可以注释掉或者删除。根据自己的理解来使用,这里我选择不使用这种方法,因为它写的都是中文的拼音。
接下来我们将iconfont.js
引入到入口文件中。
import './assets/styles/iconfont.css'
完成以上的步骤,如果没有出错的话,我们就可以开始在页面中开始使用这些图标了。
在Header.vue
文件中这样写:
<template><div class="header"><div class="header-left"><span class="iconfont"></span></div><div class="header-input"><span class="iconfont"></span>输入城市景点/游玩/主题</div><div class="header-right">城市<span class="iconfont"></span></div></div></template>
上面代码中的Unicode代码可以在iconfont官方你的项目文件中选择Unicode并复制代码,就可以得到了。这也就为什么我们前面要注释那些样式代码的原因了。
图标是有了,但是我们还需要做一些位置的优化:
<template><div class="header"><div class="header-left"><div class="iconfont back-icon"></div></div><div class="header-input"><span class="iconfont search-icon"></span>输入城市景点/游玩/主题</div><div class="header-right">城市<span class="iconfont arrow-icon"></span></div></div></template><style lang="stylus" scoped>.headerdisplay: flexcolor: #fffbackground: #00bcd4line-height: .86rem.header-leftwidth: .64remfloat: left.back-icontext-align: centerfont-size: .4rem.header-inputflex: 1background: #fffmargin-top: .12remheight: .64remline-height: .64remmargin-left: .02remborder-radius: .05rempadding-left: .2remcolor: #ccc.search-iconpadding-right: .1rem.header-rightwidth: 1.24remfloat:righttext-align: center.arrow-iconfont-size: .24rem</style>
完成以上的内容我们就可以看到现在的效果:
代码的优化:
由于我们这个项目的主题颜色,是绿色。在以后的很多组件都会用到这个颜色。我们可以定义一个变量,需要的时候直接引用就可以了。
首先需要在src/assets/styles
目录下新建一个varibles.styl
这样的一个文件,表示是一个stylus
格式的文件。并且在文件中写上一下内容。该文件可以用来写一些变量。
$bgColor = #00bcd4
下面就就要在Header.vue
文件中进行引入了。
<style lang="stylus" scoped>@import '../../../assets/styles/varibles.styl'.headerbackground: $bgColor</style>
我们发现啊,引入的文件路径太长,有什么办法可以解决呢?我们可以使用@
符号来代替路径前面的前缀。
<style lang="stylus" scoped>@import '~@/assets/styles/varibles.styl'.headerbackground: $bgColor</style>
但是呢,像assets/styles/
这样的是路径我们使用的太频繁且冗长,其实也是可以对它起个命名。我们需要在webpack-base.conf.js
文件中添加一下内容就可以了。
resolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue$': 'vue/dist/vue.esm.js','@': resolve('src'),//添加下面这一句'styles': resolve('src/assets/styles'),}}
修改完成之后,我们就可以对使用了assets/styles/
这种路径的地方进行改写为styles
就可以了。因为修改了配置文件,修改之后会出现报错的情况,所以需要重启一下服务。
main.js
import 'styles/reset.css'import 'styles/border.css'import 'styles/iconfont.css'
Header.vue
@import '~styles/varibles.styl'
最后当然也别忘记将写完的代码提交到线上仓库。使用一下命令就可以了。提交前记得先退出服务
git add .git commit -m 'addHeader'git push
总结:
首先介绍了如何去使用iconfont
图标库,然后通过对webpack
的配置对代码进行简化,也介绍了如何使用stylus
及应用stylus
变量。
首页轮播图
在企业级开发项目中,我们都是在不同的branch
分支中进行开发的,最后再合并到master分支上。所以我们需要在GitHub中创建一个轮播图的分支。
如何创建分支?
这里我们选择使用命令来创建
# 创建分支git checkout -b index-swiper# 将本地创建的分支更新到线上仓库git push origin index-swiper# 查看本地分支git branch -r# 查看当前所在分支git status
写轮播图我们选择使用那你第三方的库(vueAwesomeswiper
),该库可以帮助我们快速构建轮播效果。要使用该库需要先安装它。使用一下命令即可安装相应版本的库文件。
npm install vue-awesome-swiper@2.6.7 --save
下载好之后,我们需要将以下的代码引入到我们项目之中的mian.js
。
//引入到入口文件import Vue from 'vue'import VueAwesomeSwiper from 'vue-awesome-swiper'// require stylesimport 'swiper/dist/css/swiper.css'Vue.use(VueAwesomeSwiper, /* { default global options } */)
创建swiper
组件:在components文件夹中新建Swiper.vue
文件。并且写上如下的代码:
<template><swiper :options="swiperOption"><img class="swiper-image" src="http://mp-piao-/mp_piao_admin_mp_piao_admin/admin/1/8c458e9fe5b5f4575b1d7ec0489a9ff8.jpg_750x200_f0fbf511.jpg"></swiper-slide><swiper-slide><img class="swiper-image" src="http://mp-piao-/mp_piao_admin_mp_piao_admin/admin/12/d6df0db510d7b9aaa3d9ce4cffafeca1.jpg_750x200_abb38f14.jpg"></swiper-slide><div class="swiper-pagination" slot="pagination"></div>//<div class="swiper-button-prev" slot="button-prev"></div>//<div class="swiper-button-next" slot="button-next"></div>//<div class="swiper-scrollbar" slot="scrollbar"></div></swiper></template><script>export default {name: 'HomeSwiper',data(){return {swiperOption: {}}}}</script><style lang='stylus' scoped>.swiper-imagewidth:100%</style>
这里再介绍一个问题:就是在加载很慢的时候,轮播以下的内容,会有明显的抖动为题。要解决这个问题只需做一下修改就可以了。
<template><div class="wrapper"> //....</div></template><style lang='stylus' scoped>.wrapperoverflow: hiddenwidth: 100%height: 0// 26.67%是图片的宽高比padding-bottom: 26.67%background: #eee.swiper-imagewidth:100%</style>
接下来我们我给轮播图加控制点及进行相关的优化;
<template><div class="wrapper"> <swiper :options="swiperOption"><swiper-slide v-for="item of swiperList" :key="item.id"><img class="swiper-image" :src="item.imgUrl"></swiper-slide><div class="swiper-pagination" slot="pagination"></div></swiper></div></template><script>export default {name: 'HomeSwiper',data(){return {swiperOption: {pagination: '.swiper-pagination', //显示控制点loop:true //让轮播可以循环的切换},swiperList:[{id:'001',imgUrl:'http://mp-piao-/mp_piao_admin_mp_piao_admin/admin/1/8c458e9fe5b5f4575b1d7ec0489a9ff8.jpg_750x200_f0fbf511.jpg'},{id:'002',imgUrl:'http://mp-piao-/mp_piao_admin_mp_piao_admin/admin/12/d6df0db510d7b9aaa3d9ce4cffafeca1.jpg_750x200_abb38f14.jpg'}]}}}</script><style lang='stylus' scoped>// 这是改变控制点的默认颜色,'>>>'代表穿透 在wrapper下只要出现后面的样式就会执行.wrapper >>> .swiper-pagination-bullet-activebackground: #fff.wrapperoverflow: hiddenwidth: 100%height: 0// 26.67%是图片的宽高比padding-bottom: 26.67%background: #eee.swiper-imagewidth:100%</style>
完成以上内容就可以把完成的代码提交到线上仓库。再将当前的分支内容合并到master
分支中。
# 切换到主分支中git checkout master# 合并分支git merge origin/index-swiper# 最后提交git push
在实际开发过程中,都是按照以上的步骤来进行的。
图标区域的布局
同前面讲的操作,这一节我们也要新建一个分支来进行开发新的内容。创建分支就不再累述,直接查看前面章节即可。
创建Icons
组件:在components文件夹中新建Icons.vue
文件。并且写上如下的代码:
<template><div>icon</div></template><script>export default {name:'HomeIcons'}</script><style lang="stylus" scoped></style>
Home.vue
<template><div><home-header></home-header><home-swiper></home-swiper><home-icons></home-icons></div></template><script>import HomeHeader from './components/Header'import HomeSwiper from './components/Swiper'import HomeIcons from './components/Icons'export default {name: 'Home',components: {HomeHeader, HomeSwiper,HomeIcons}}</script>
完整Icons.vue
代码:
<template><div class="icons"><div class="icon"><div class="icon-img"><img class="icon-img-content" src="/piao/fusion/1803/95/f3dd6c383aeb3b02.png"></div><p class="icon-desc">景点门票</p></div></div></template><script>export default {name:'HomeIcons'}</script><style lang="stylus" scoped>@import '~styles/varibles.styl'.iconsoverflow: hiddenheight: 0padding-bottom: 50%.iconposition:relativefloat: leftwidth: 25%height:0padding-bottom: 25%.icon-imgposition:absolutetop:0left:0right:0bottom:.44rembox-sizing:border-boxpadding:.1rem.icon-img-contentdisplay:blockmargin:0 autoheight:100%.icon-descposition:absoluteleft:0right:0bottom:0height:.44remline-height:.44remtext-align:centercolor:$darkTextColor</style>
图标区域逻辑实现
需求:当图标很多的时候,可以实现左右滑动的效果。以及循环渲染数据。
完整代码:
<template><div class="icons"><swiper :options="swiperOption"><!-- 循环渲染数据 添加swiper-slide标签可以实现左右滑动 --><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><div class="swiper-pagination" slot="pagination"></div></swiper></div></template><script>export default {name:'HomeIcons',data (){return {// 显示控制点swiperOption: {pagination: '.swiper-pagination'},// 将数据写在data中 视图层直接循环渲染iconList:[{id:'001',imgUrl:'/piao/fusion/1803/95/f3dd6c383aeb3b02.png',desc:'景点门票'},{id:'002',imgUrl:'/piao/fusion/1803/47/c2b659e048b11602.png',desc:'主题乐园'},{id:'003',imgUrl:'/piao/fusion/1803/ab/6f7d6e44963c9302.png',desc:'泡温泉'},{id:'004',imgUrl:'http://mp-piao-/mp_piao_admin_mp_piao_admin/admin/3/a40ee278d67000f2a29d2e20f6a029b3.png',desc:'自然风光'},{id:'005',imgUrl:'/piao/fusion/1804/5a/13ceb38dcf262f02.png',desc:'一日游'},{id:'006',imgUrl:'/piao/fusion/1803/97/02f5043b51b2102.png',desc:'鼓浪屿'},{id:'007',imgUrl:'/piao/fusion/1803/96/c70f1e85ae4a4f02.png',desc:'武夷山'},{id:'008',imgUrl:'/piao/fusion/1803/50/26ffa31b56646402.png',desc:'亲子游'},{id:'009',imgUrl:'/piao/fusion/1803/b8/c5dcdb58deec2402.png',desc:'玩转贵安'},{id:'010',imgUrl:'/piao/fusion/1803/b6/37560ece9c62b502.png',desc:'城市观光'}]}},// 通过计算属性来计算需要轮播的页数computed:{pages (){const pages = []this.iconList.forEach((item,index) => {//假如index=3 通过向上取整page=0 则该index展示在第一页 index>=8 则展示在第二页const page = Math.floor(index/8)if (!pages) {pages = []}pages.push(item)}) return pages}}}</script><style lang="stylus" scoped>@import '~styles/varibles.styl'.icons >>> .swiper-containeroverflow: hiddenheight: 0padding-bottom: 60%.iconposition:relativefloat: leftwidth: 25%height:0padding-bottom: 25%.icon-imgposition:absolutetop:0left:0right:0bottom:.44rembox-sizing:border-boxpadding:.1rem.icon-img-contentdisplay:blockmargin:0 autoheight:100%.icon-descposition:absoluteleft:0right:0bottom:0height:.44rempadding:.1remline-height:.44remtext-align:centercolor:$darkTextColor// 当文字描述过长,显示省略点overflow:hiddenwhite-space:nowraptext-overflow:ellipsis</style>
提交到线上仓库:
git add .git commit -m 'desc'git push --set-upstream origin index-iconsgit checkout master# 合并到master分支git merge origin/index-iconsgit push
开发推荐组件
老规矩创建分支"index-recommend",创建Recommend
组件:在components文件夹中新建Recommend.vue
文件。记得要在Home.vue
中引用该组件。
完整代码:
<template><div><div class="recommend-title">猜你喜欢</div><ul><li :key='item.id' class="item border-bottom" v-for="item of recommendList"><img class="item-img" :src="item.imgUrl"><div class="item-info"><p class="item-title">{{item.title}}</p><p class="item-desc">{{item.desc}}</p><button class="item-button">查看详情</button></div></li></ul></div></template><script>export default {name: 'HomeRecommend',data (){return {recommendList:[{id:'001',imgUrl:'/sight/p0/1703/9d/9dd5987e5c7701f2a3.water.jpg_200x200_8b16531c.jpg',title:'福州明谷行馆',desc:'最好玩的地方'},{id:'002',imgUrl:'/sight/p0/1703/9d/9dd5987e5c7701f2a3.water.jpg_200x200_8b16531c.jpg',title:'福州明谷行馆',desc:'最好玩的地方'},{id:'003',imgUrl:'/sight/p0/1703/9d/9dd5987e5c7701f2a3.water.jpg_200x200_8b16531c.jpg',title:'福州明谷行馆',desc:'最好玩的地方'}]}}}</script><style lang="stylus" scoped>@import '~styles/varibles.styl'.recommend-titlemargin-top:.2remline-height: .8rembackground: #eeetext-indent:.2rem.itemoverflow:hiddendisplay:flexheight:1.9rem.item-imgwidth:1.7remheight:1.7rempadding:.1rem.item-infoflex:1padding:.1remmin-width:0.item-titleline-height:.54remfont-size:.32remellipsis().item-descline-height:.4remcolor:#cccellipsis().item-buttonmargin-top:.16remcolor:#fffline-height:.44rembackground:#ff9300padding:0 .1remborder-radius:.06rem</style>
周末去哪儿
还是在同样的分支下进行开发,只不过需要新建一个组件’Weekend.vue’。
完整代码:
<template><div><div class="recommend-title">周末去哪儿</div><ul><li :key='item.id' class="item border-bottom" v-for="item of recommendList"><div class="item-img-wrapper"><img class="item-img" :src="item.imgUrl"></div><div class="item-info"><p class="item-title">{{item.title}}</p><p class="item-desc">{{item.desc}}</p></div></li></ul></div></template><script>export default {name: 'HomeWeekend',data (){return {recommendList:[{id:'001',imgUrl:'/sight/source/1505/68/de862f94e383a6.jpg_r_640x214_f9df927b.jpg',title:'福州明谷行馆',desc:'最好玩的地方'},{id:'002',imgUrl:'/sight/source/1505/68/de862f94e383a6.jpg_r_640x214_f9df927b.jpg',title:'福州明谷行馆',desc:'最好玩的地方'},{id:'003',imgUrl:'/sight/source/1505/68/de862f94e383a6.jpg_r_640x214_f9df927b.jpg',title:'福州明谷行馆',desc:'最好玩的地方'}]}}}</script><style lang="stylus" scoped>@import '~styles/varibles.styl'.recommend-titlemargin-top:.2remline-height: .8rembackground: #eeetext-indent:.2rem.item-img-wrapperoverflow:hiddenheight:0padding-bottom:33.9%.item-imgwidth:100%.item-infoflex:1padding:.1remmin-width:0.item-titleline-height:.54remfont-size:.32remellipsis().item-descline-height:.4remcolor:#cccellipsis()</style>
Ajax获取首页数据
在前面的章节中,我们都是把数据给写死的,这节我们使用ajax动态的获取数据。
第一步还是先创建分支"index-ajax“。在vue中发送ajax可以由很多的工具供我们使用,比如fetch
、vue-resource
、现在vue官方推荐使用axios
,在于它非常的强大,它可以跨平台的发送请求。在浏览器可以帮你发送XHR的请求,在node服务器可以发送HTTP的请求。我们就用axios。
# 安装axiosnpm install axios --save
在一个网站中,一般都由n个组件组成,如果每个组件都发送一个请求的话,显然性能是非常的低。所以我们一般都是写在Home组件中统一发送一个。
在使用ajax的时候,作为前端的我们再没有获得数据文件之前,我们都是使用模拟的数据。所以请求的地址一般都是些的本地文件。但是在实际的开发环境中,需要把地址替换一下,但是在上线之前去修改代码时有风险的,那要怎么做才能解决一些问题?
我们可以在项目中的config/index.js
文件中的proxyTable
属性中去添加以下内容,表示但我们请求服务器地址的时候,但时服务器有没有找到该文件,vue则会自动的代替为我们的本地地址文件。
//该功能为webpack-dev-server提供proxyTable: {'/api':{target: 'http://localhost:8080',pathRewrite:{//当访问以api开头的就跳转'^/api':'/static/mock'}}}
我们现在本地创建一个模拟的数据。模拟的数据我们放在static
文件夹之下。我们新建一个文件夹mock
以及mock下的index.json
文件。
由于该文件是我们自己创建的数据,并不希望把它提交到我们的仓库中去,所以我们可以在gitignore
中把该文件给忽略。
添加该文件夹路径即可static/mock
Home.vue
<script>//....省略了其他组件代码import axios from 'axios'export default {name: 'Home',components: {HomeHeader, HomeSwiper,HomeIcons,HomeRecommend,HomeWeekend},methods:{getHomeInfo (){// 虽然这里我们写的是api的开头的地址,但是vue已经帮我们跳转到了我们本地的json文件axios.get('/api/index.json').then(this.getHomeInfoSucc)},getHomeInfoSucc (res){//成功获取到本地json数据console.log(res)}},mounted (){this.getHomeInfo()}}</script>
首页父子组件数据传递
接着上一节的内容写,我们直接先Home.vue中写。在data函数中定义相关的变量,并将这些变量传递给组件
<template><div><home-header :city="city"></home-header><home-swiper :list="swiperList"></home-swiper><home-icons :list="iconsList"></home-icons><home-recommend :list="recommendList"></home-recommend><home-weekend :list="weenkendList"></home-weekend></div></template><script>data (){return {city:"",swiperList:[],iconsList:[],recommendList:[],weenkendList:[]}}</script>
再到各组件中改写一下:把我们前面写死的代码都给删除了。使用props
接收一下传递过来的值。
<template><div class="wrapper"><!-- v-if 的作用是解决轮播默认显示的最后一张,因为在没有获取到数据之前,渲染是一个空数组。判断如果是空数组就不进行渲染 --><swiper :options="swiperOption" v-if="showSwiper"><!-- 循环的数据来源于接收到的数组 --><swiper-slide v-for="item of list" :key="item.id"><img class="swiper-image" :src="item.imgUrl"></swiper-slide><div class="swiper-pagination" slot="pagination"></div></swiper></div></template><script>export default {name: 'HomeSwiper',props: {list:Array},data(){return {swiperOption: {pagination: '.swiper-pagination',loop:true,//使用autoplay就可以实现间隔3秒自动切换轮播图autoplay:3000}}},computed:{showSwiper(){return this.list.length}}}</script>
其它的组件也是同上面的写法一致。模仿着写就可以了。